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



6509
6510
6511
6512
6513
6514
6515
6516
6517
6518
# File 'lib/parse/query.rb', line 6509

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.



6641
6642
6643
6644
6645
6646
6647
6648
# File 'lib/parse/query.rb', line 6641

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.



6619
6620
6621
# File 'lib/parse/query.rb', line 6619

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.



6667
6668
6669
6670
6671
6672
6673
6674
# File 'lib/parse/query.rb', line 6667

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.



6655
6656
6657
6658
6659
6660
6661
6662
# File 'lib/parse/query.rb', line 6655

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)


6534
6535
6536
6537
6538
6539
6540
6541
6542
6543
6544
6545
6546
6547
6548
6549
6550
6551
6552
6553
6554
6555
6556
6557
6558
6559
# File 'lib/parse/query.rb', line 6534

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



6574
6575
6576
6577
6578
6579
6580
6581
6582
6583
6584
6585
6586
6587
6588
6589
6590
6591
6592
6593
6594
6595
6596
6597
6598
6599
6600
6601
6602
6603
6604
6605
6606
6607
6608
6609
6610
6611
6612
# File 'lib/parse/query.rb', line 6574

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)


6564
6565
6566
# File 'lib/parse/query.rb', line 6564

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.



6629
6630
6631
6632
6633
6634
6635
6636
# File 'lib/parse/query.rb', line 6629

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