Class: Parse::GroupByDate

Inherits:
Object
  • Object
show all
Defined in:
lib/parse/query.rb

Overview

Helper class for handling group_by_date aggregations with method chaining. Groups data by time intervals (year, month, week, day, hour) and supports aggregation operations.

Direct Known Subclasses

SortableGroupByDate

Instance Method Summary collapse

Constructor Details

#initialize(query, date_field, interval, return_pointers: false, timezone: nil, mongo_direct: false) ⇒ GroupByDate

Returns a new instance of GroupByDate.

Parameters:

  • query (Parse::Query)

    the base query to group

  • date_field (Symbol, String)

    the date field to group by

  • interval (Symbol)

    the time interval (:year, :month, :week, :day, :hour, :minute)

  • timezone (String) (defaults to: nil)

    the timezone for date operations (e.g., “America/New_York”, “+05:00”)

  • mongo_direct (Boolean) (defaults to: false)

    whether to query MongoDB directly bypassing Parse Server



6479
6480
6481
6482
6483
6484
6485
6486
6487
6488
# File 'lib/parse/query.rb', line 6479

def initialize(query, date_field, interval, return_pointers: false, timezone: nil, mongo_direct: false)
  @query = query
  @date_field = date_field
  @interval = interval
  @return_pointers = return_pointers
  @timezone = timezone
  @mongo_direct = mongo_direct
  @sort_target = nil    # nil | :key | :value (no :size — date groupings have no list accumulator yet)
  @sort_direction = nil # :asc | :desc
end

Instance Method Details

#average(field) ⇒ Hash Also known as: avg

Calculate average of a field for each time period.

Parameters:

  • field (Symbol, String)

    the field to average within each time period.

Returns:

  • (Hash)

    a hash with formatted date strings as keys and averages as values.



6611
6612
6613
6614
6615
6616
6617
6618
# File 'lib/parse/query.rb', line 6611

def average(field)
  if field.nil? || !field.respond_to?(:to_s)
    raise ArgumentError, "Invalid field name passed to `average`."
  end

  formatted_field = @query.send(:format_aggregation_field, field)
  execute_date_aggregation("average", { "$avg" => "$#{formatted_field}" })
end

#countHash

Count the number of items in each time period.

Examples:

Post.group_by_date(:created_at, :day).count
# => {"2024-11-24" => 45, "2024-11-25" => 23}

Returns:

  • (Hash)

    a hash with formatted date strings as keys and counts as values.



6589
6590
6591
# File 'lib/parse/query.rb', line 6589

def count
  execute_date_aggregation("count", { "$sum" => 1 })
end

#max(field) ⇒ Hash

Find maximum value of a field for each time period.

Parameters:

  • field (Symbol, String)

    the field to find maximum for within each time period.

Returns:

  • (Hash)

    a hash with formatted date strings as keys and maximum values as values.



6637
6638
6639
6640
6641
6642
6643
6644
# File 'lib/parse/query.rb', line 6637

def max(field)
  if field.nil? || !field.respond_to?(:to_s)
    raise ArgumentError, "Invalid field name passed to `max`."
  end

  formatted_field = @query.send(:format_aggregation_field, field)
  execute_date_aggregation("max", { "$max" => "$#{formatted_field}" })
end

#min(field) ⇒ Hash

Find minimum value of a field for each time period.

Parameters:

  • field (Symbol, String)

    the field to find minimum for within each time period.

Returns:

  • (Hash)

    a hash with formatted date strings as keys and minimum values as values.



6625
6626
6627
6628
6629
6630
6631
6632
# File 'lib/parse/query.rb', line 6625

def min(field)
  if field.nil? || !field.respond_to?(:to_s)
    raise ArgumentError, "Invalid field name passed to `min`."
  end

  formatted_field = @query.send(:format_aggregation_field, field)
  execute_date_aggregation("min", { "$min" => "$#{formatted_field}" })
end

#order(spec) ⇒ self

Order date-grouped results by the date key or by the aggregated value. When no ordering is configured the default is chronological by date (the original behavior). The configured order is pushed into the pipeline as a ‘$sort` stage.

Examples:

Newest periods first

Post.group_by_date(:created_at, :day).order(key: :desc).count

Busiest day first

Post.group_by_date(:created_at, :day).order(value: :desc).count

Parameters:

  • spec (Hash, Symbol)

    one of:

    • ‘{ key: :asc | :desc }` — date order (asc is chronological)

    • ‘{ value: :asc | :desc }` — by aggregated value (count/sum/…)

    • ‘:asc`/`:desc` shorthand for `{ key: direction }`

Returns:

  • (self)


6504
6505
6506
6507
6508
6509
6510
6511
6512
6513
6514
6515
6516
6517
6518
6519
6520
6521
6522
6523
6524
6525
6526
6527
6528
6529
# File 'lib/parse/query.rb', line 6504

def order(spec)
  target, direction =
    case spec
    when Symbol
      [:key, spec]
    when Hash
      unless spec.size == 1
        raise ArgumentError, "order(...) expects a single pair, e.g. {value: :desc} (got #{spec.inspect})"
      end
      k, v = spec.first
      [k.to_sym, v.to_sym]
    else
      raise ArgumentError, "order(...) expects {key:|value: => :asc|:desc} or :asc/:desc (got #{spec.inspect})"
    end

  unless %i[key value].include?(target)
    raise ArgumentError, "order(...) target must be :key or :value for date groupings (got #{target.inspect})"
  end
  unless %i[asc desc].include?(direction)
    raise ArgumentError, "order(...) direction must be :asc or :desc (got #{direction.inspect})"
  end

  @sort_target = target
  @sort_direction = direction
  self
end

#pipelineArray<Hash>

Returns the MongoDB aggregation pipeline that would be used for a count operation. This is useful for debugging and understanding the generated pipeline.

Examples:

Post.where(:author_workspace.eq => workspace).group_by_date(:created_at, :month).pipeline
# => [{"$match"=>{"authorWorkspace"=>"Workspace$abc123"}}, {"$group"=>{"_id"=>{"year"=>{"$year"=>"$createdAt"}, "month"=>{"$month"=>"$createdAt"}}, "count"=>{"$sum"=>1}}}, {"$project"=>{"_id"=>0, "objectId"=>"$_id", "count"=>1}}]

Returns:

  • (Array<Hash>)

    the MongoDB aggregation pipeline



6544
6545
6546
6547
6548
6549
6550
6551
6552
6553
6554
6555
6556
6557
6558
6559
6560
6561
6562
6563
6564
6565
6566
6567
6568
6569
6570
6571
6572
6573
6574
6575
6576
6577
6578
6579
6580
6581
6582
# File 'lib/parse/query.rb', line 6544

def pipeline
  # Format the date field name
  formatted_date_field = @query.send(:format_aggregation_field, @date_field)

  # Build the aggregation pipeline (same logic as execute_date_aggregation)
  pipeline = []

  # Add match stage if there are where conditions
  compiled_where = @query.send(:compile_where)
  if compiled_where.present?
    # Convert field names for aggregation context and handle dates
    aggregation_where = @query.send(:convert_constraints_for_aggregation, compiled_where)
    stringified_where = @query.send(:convert_dates_for_aggregation, aggregation_where)
    pipeline << { "$match" => stringified_where }
  end

  # Create date grouping expression based on interval using shared method
  date_expr = build_date_group_expression(formatted_date_field)

  # Add group, sort, and project stages (using count as example aggregation)
  pipeline.concat([
    {
      "$group" => {
        "_id" => date_expr,
        "count" => { "$sum" => 1 },
      },
    },
    sort_stage,
    {
      "$project" => {
        "_id" => 0,
        "objectId" => "$_id",
        "count" => 1,
      },
    },
  ])

  pipeline
end

#sort(direction = :asc) ⇒ self

Sort date-grouped results by date key (Ruby ‘Hash#sort` default).

Parameters:

  • direction (Symbol) (defaults to: :asc)

    ‘:asc` (default, chronological) or `:desc`

Returns:

  • (self)


6534
6535
6536
# File 'lib/parse/query.rb', line 6534

def sort(direction = :asc)
  order(direction)
end

#sum(field) ⇒ Hash

Sum a field for each time period.

Examples:

Document.group_by_date(:created_at, :month).sum(:file_size)
# => {"2024-11" => 1024000, "2024-12" => 512000}

Parameters:

  • field (Symbol, String)

    the field to sum within each time period.

Returns:

  • (Hash)

    a hash with formatted date strings as keys and sums as values.



6599
6600
6601
6602
6603
6604
6605
6606
# File 'lib/parse/query.rb', line 6599

def sum(field)
  if field.nil? || !field.respond_to?(:to_s)
    raise ArgumentError, "Invalid field name passed to `sum`."
  end

  formatted_field = @query.send(:format_aggregation_field, field)
  execute_date_aggregation("sum", { "$sum" => "$#{formatted_field}" })
end