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



7259
7260
7261
7262
7263
7264
7265
7266
7267
7268
# File 'lib/parse/query.rb', line 7259

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.



7390
7391
7392
7393
7394
7395
7396
7397
# File 'lib/parse/query.rb', line 7390

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.



7368
7369
7370
# File 'lib/parse/query.rb', line 7368

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.



7416
7417
7418
7419
7420
7421
7422
7423
# File 'lib/parse/query.rb', line 7416

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.



7404
7405
7406
7407
7408
7409
7410
7411
# File 'lib/parse/query.rb', line 7404

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)


7284
7285
7286
7287
7288
7289
7290
7291
7292
7293
7294
7295
7296
7297
7298
7299
7300
7301
7302
7303
7304
7305
7306
7307
7308
# File 'lib/parse/query.rb', line 7284

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



7323
7324
7325
7326
7327
7328
7329
7330
7331
7332
7333
7334
7335
7336
7337
7338
7339
7340
7341
7342
7343
7344
7345
7346
7347
7348
7349
7350
7351
7352
7353
7354
7355
7356
7357
7358
7359
7360
7361
# File 'lib/parse/query.rb', line 7323

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)


7313
7314
7315
# File 'lib/parse/query.rb', line 7313

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.



7378
7379
7380
7381
7382
7383
7384
7385
# File 'lib/parse/query.rb', line 7378

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