Module: CalculateAll

Defined in:
lib/calculate-all.rb,
lib/calculate-all/helpers.rb,
lib/calculate-all/version.rb,
lib/calculate-all/querying.rb

Defined Under Namespace

Modules: Helpers, Querying

Constant Summary collapse

VERSION =
"0.2.2"

Instance Method Summary collapse

Instance Method Details

#calculate_all(*function_aliases, **functions, &block) ⇒ Object

Calculates multiple aggregate values on a scope in one request, similarly to #calculate



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/calculate-all.rb', line 6

def calculate_all(*function_aliases, **functions, &block)
  # If only one aggregate is given without explicit naming,
  # return row(s) directly without wrapping in Hash
  if function_aliases.size == 1 && functions.size == 0
    return_plain_values = true
  end

  # Convert the function_aliases to actual SQL
  functions.merge!(CalculateAll::Helpers.decode_function_aliases(function_aliases))

  # Check if any functions are given
  if functions == {}
    raise ArgumentError, "provide at least one function to calculate"
  end

  columns = (group_values.map(&:to_s) + functions.values).map { |sql| Arel.sql(sql) }
  results = {}
  pluck(*columns).each do |row|
    # If pluck called without any groups and with a single argument,
    # it will return an array of simple results instead of array of arrays
    if functions.size == 1 && group_values.size == 0
      row = [row]
    end

    key = if group_values.size == 0
      :ALL
    elsif group_values.size == 1
      # If only one group is provided, the resulting key is just a scalar value
      row.shift
    else
      # if multiple groups, the key will be an array.
      row.shift(group_values.size)
    end

    value = if return_plain_values
      row.last
    else
      # it is possible to have more actual group values returned than group_values.size
      functions.keys.zip(row.last(functions.size)).to_h
    end

    results[key] = value
  end

  # Additional groupdate magic of filling empty periods with defaults
  if defined?(Groupdate.process_result)
    # Since that hash is the same instance for every backfilled raw, at least
    # freeze it to prevent surprize modifications in calling code.
    default_value = return_plain_values ? nil : {}.freeze
    results = Groupdate.process_result(self, results, default_value: default_value)
  end

  if block
    results.transform_values! do |value|
      return_plain_values ? block.call(value) : block.call(**value)
    end
  end

  # Return unwrapped hash directly for scope without any .group()
  if group_values.empty?
    results[:ALL]
  else
    results
  end
end