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



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

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.



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

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.



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

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.



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

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.



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

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)


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

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



7397
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
# File 'lib/parse/query.rb', line 7397

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)


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

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.



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

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