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



7334
7335
7336
7337
7338
7339
7340
7341
7342
7343
# File 'lib/parse/query.rb', line 7334

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.



7465
7466
7467
7468
7469
7470
7471
7472
# File 'lib/parse/query.rb', line 7465

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.



7443
7444
7445
# File 'lib/parse/query.rb', line 7443

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.



7491
7492
7493
7494
7495
7496
7497
7498
# File 'lib/parse/query.rb', line 7491

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.



7479
7480
7481
7482
7483
7484
7485
7486
# File 'lib/parse/query.rb', line 7479

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)


7359
7360
7361
7362
7363
7364
7365
7366
7367
7368
7369
7370
7371
7372
7373
7374
7375
7376
7377
7378
7379
7380
7381
7382
7383
# File 'lib/parse/query.rb', line 7359

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



7398
7399
7400
7401
7402
7403
7404
7405
7406
7407
7408
7409
7410
7411
7412
7413
7414
7415
7416
7417
7418
7419
7420
7421
7422
7423
7424
7425
7426
7427
7428
7429
7430
7431
7432
7433
7434
7435
7436
# File 'lib/parse/query.rb', line 7398

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)


7388
7389
7390
# File 'lib/parse/query.rb', line 7388

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.



7453
7454
7455
7456
7457
7458
7459
7460
# File 'lib/parse/query.rb', line 7453

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