calculate-all 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +3 -3
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -0
- data/README.md +18 -5
- data/calculate-all.gemspec +1 -1
- data/gemfiles/activerecord42.gemfile +1 -1
- data/gemfiles/{activerecord51.gemfile → activerecord71.gemfile} +3 -3
- data/lib/calculate-all/version.rb +1 -1
- data/lib/calculate-all.rb +53 -44
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3928cde50fac04c8f8602ab9e83c685d190e8466febbb2c0314cf962182f99c
|
4
|
+
data.tar.gz: 83f9636ffda1f25e0faabc5454a4c731648371ba20572e5df53357f72e9b5708
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a13ddf4a236a27bebbdf1bf7f316c1137eff5eb6cfb3a0589a0f91dfa0548b6167cc4441173ca0ce4b174a156092aa50185ec5f111e54f6c0e75ee3c738d430
|
7
|
+
data.tar.gz: 95f229487220d5bb23ef2a6a50b9aef7a782e25b627c3d87716690a0673fc6890e28c3ab023481f475837a0d595baac84078ef31841c0e3b135081c61eda47b9
|
data/.github/workflows/build.yml
CHANGED
@@ -8,6 +8,8 @@ jobs:
|
|
8
8
|
include:
|
9
9
|
- ruby: "3.4"
|
10
10
|
gemfile: Gemfile
|
11
|
+
- ruby: "3.2"
|
12
|
+
gemfile: gemfiles/activerecord71.gemfile
|
11
13
|
- ruby: "3.1"
|
12
14
|
gemfile: gemfiles/activerecord70.gemfile
|
13
15
|
- ruby: "3.0"
|
@@ -16,11 +18,9 @@ jobs:
|
|
16
18
|
gemfile: gemfiles/activerecord60.gemfile
|
17
19
|
- ruby: "2.6"
|
18
20
|
gemfile: gemfiles/activerecord52.gemfile
|
19
|
-
- ruby: "2.6"
|
20
|
-
gemfile: gemfiles/activerecord51.gemfile
|
21
21
|
- ruby: "2.6"
|
22
22
|
gemfile: gemfiles/activerecord50.gemfile
|
23
|
-
- ruby: "2.
|
23
|
+
- ruby: "2.6"
|
24
24
|
gemfile: gemfiles/activerecord42.gemfile
|
25
25
|
runs-on: ubuntu-latest
|
26
26
|
env:
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -8,7 +8,10 @@ in a single request, with support for grouping.
|
|
8
8
|
|
9
9
|
Should be useful for dashboards, timeseries stats, and charts.
|
10
10
|
|
11
|
-
Currently tested with PostgreSQL, MySQL and SQLite3, ruby >= 2.
|
11
|
+
Currently tested with PostgreSQL, MySQL and SQLite3, ruby >= 2.6, rails >= 4, groupdate >= 4.
|
12
|
+
|
13
|
+
With rails >= 7.1, `#async_calculate_all` is also available, which does the same but in a separate
|
14
|
+
db connection and ruby thread, and returns `ActiveRecord::Promise`
|
12
15
|
|
13
16
|
## Usage
|
14
17
|
|
@@ -48,6 +51,16 @@ stats = Order.group(:department_id).group(:payment_method).order(:payment_method
|
|
48
51
|
# }
|
49
52
|
```
|
50
53
|
|
54
|
+
```ruby
|
55
|
+
stats_by_department = Order.group(:department_id).async_calculate_all(:count, :price_sum)
|
56
|
+
# continue with some other expensive stuff
|
57
|
+
|
58
|
+
# later, in some view
|
59
|
+
stats_by_department.value.each do |department_id, stats|
|
60
|
+
# ...
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
51
64
|
## Rationale
|
52
65
|
|
53
66
|
Active Record makes it really easy to use most common database aggregate functions like COUNT(), MAX(), MIN(), AVG(), SUM().
|
@@ -78,7 +91,7 @@ or arbitrary symbols and keyword arguments with SQL snippets, aggregate function
|
|
78
91
|
)
|
79
92
|
```
|
80
93
|
|
81
|
-
For convenience, `calculate_all(:count, :avg_column)` is the same as `
|
94
|
+
For convenience, `calculate_all(:count, :avg_column)` is the same as `calculate_all(count: :count, avg_column: :avg_column)`
|
82
95
|
|
83
96
|
Here's a cheatsheet of recognized shortcuts:
|
84
97
|
|
@@ -139,7 +152,7 @@ with aggregate functions.
|
|
139
152
|
```ruby
|
140
153
|
Order.group(:payment_method).calculate_all('CAST(SUM(price) AS decimal) / COUNT(DISTINCT user_id)')
|
141
154
|
# => {
|
142
|
-
# "card" => 0.524e3
|
155
|
+
# "card" => 0.524e3,
|
143
156
|
# "cash" => 0.132e3
|
144
157
|
# }
|
145
158
|
```
|
@@ -186,7 +199,7 @@ Order.group_by_year(:created_at).calculate_all(*Stats.members, &Stats.method(:ne
|
|
186
199
|
# => {
|
187
200
|
# Wed, 01 Jan 2014 => #<data Stats count=2, max_price=700>,
|
188
201
|
# Thu, 01 Jan 2015 => #<data Stats count=0, max_price=nil>,
|
189
|
-
# Fri, 01 Jan 2016 => #<data Stats count=3,
|
202
|
+
# Fri, 01 Jan 2016 => #<data Stats count=3, max_price=800>
|
190
203
|
# }
|
191
204
|
```
|
192
205
|
|
@@ -227,7 +240,7 @@ Or install it yourself as:
|
|
227
240
|
|
228
241
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
229
242
|
Run `BUNDLE_GEMFILE=gemfiles/activerecord60.gemfile bundle` then `BUNDLE_GEMFILE=gemfiles/activerecord60.gemfile rake`
|
230
|
-
to test
|
243
|
+
to test against specific active record version.
|
231
244
|
|
232
245
|
To experiment you can load a test database and jump to IRB with
|
233
246
|
|
data/calculate-all.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.required_ruby_version =
|
21
|
+
spec.required_ruby_version = ">= 2.6.0"
|
22
22
|
|
23
23
|
spec.add_dependency "activesupport", ">= 4.0.0"
|
24
24
|
end
|
@@ -5,8 +5,8 @@ gemspec path: ".."
|
|
5
5
|
gem "rake"
|
6
6
|
gem "minitest"
|
7
7
|
gem "minitest-reporters"
|
8
|
-
gem "activerecord", "~>
|
9
|
-
gem "groupdate", "~>
|
8
|
+
gem "activerecord", "~> 7.1.0"
|
9
|
+
gem "groupdate", "~> 6.0.0"
|
10
10
|
gem "pg"
|
11
11
|
gem "mysql2"
|
12
|
-
gem "sqlite3"
|
12
|
+
gem "sqlite3", "~> 1.4"
|
data/lib/calculate-all.rb
CHANGED
@@ -11,7 +11,7 @@ module CalculateAll
|
|
11
11
|
return_plain_values = true
|
12
12
|
end
|
13
13
|
|
14
|
-
named_expressions = expression_shortcuts.
|
14
|
+
named_expressions = expression_shortcuts.index_by(&:itself).merge(named_expressions)
|
15
15
|
|
16
16
|
named_expressions.transform_values! do |shortcut|
|
17
17
|
Helpers.decode_expression_shortcut(shortcut, group_values)
|
@@ -25,48 +25,55 @@ module CalculateAll
|
|
25
25
|
value_mapping = named_expressions.transform_values { |column| columns.index(column) }
|
26
26
|
columns.map! { |column| column.is_a?(String) ? Arel.sql(column) : column }
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
key = if group_values.size == 0
|
35
|
-
:ALL
|
36
|
-
elsif group_values.size == 1
|
37
|
-
# If only one group is provided, the resulting key is just a scalar value
|
38
|
-
row.first
|
39
|
-
else
|
40
|
-
# if multiple groups, the key will be an array.
|
41
|
-
row.first(group_values.size)
|
42
|
-
end
|
28
|
+
pluck(*columns).then do |rows|
|
29
|
+
results = rows.to_h do |row|
|
30
|
+
# If pluck called with a single argument
|
31
|
+
# it will return an array of scalars instead of array of arrays
|
32
|
+
row = [row] if columns.size == 1
|
43
33
|
|
44
|
-
|
34
|
+
key = if group_values.size == 0
|
35
|
+
:ALL
|
36
|
+
elsif group_values.size == 1
|
37
|
+
# If only one group is provided, the resulting key is just a scalar value
|
38
|
+
row.first
|
39
|
+
else
|
40
|
+
# if multiple groups, the key will be an array.
|
41
|
+
row.first(group_values.size)
|
42
|
+
end
|
45
43
|
|
46
|
-
|
44
|
+
value = value_mapping.transform_values { |index| row[index] }
|
47
45
|
|
48
|
-
|
49
|
-
end
|
46
|
+
value = value.values.last if return_plain_values
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
48
|
+
[key, value]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Additional groupdate magic of filling empty periods with defaults
|
52
|
+
if defined?(Groupdate.process_result)
|
53
|
+
# Since that hash is the same instance for every backfilled row, at least
|
54
|
+
# freeze it to prevent surprise modifications across multiple rows in the calling code.
|
55
|
+
default_value = return_plain_values ? nil : {}.freeze
|
56
|
+
results = Groupdate.process_result(self, results, default_value: default_value)
|
57
|
+
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
if block
|
60
|
+
results.transform_values! do |value|
|
61
|
+
return_plain_values ? block.call(value) : block.call(**value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return unwrapped hash directly for scope without any .group()
|
66
|
+
if group_values.empty?
|
67
|
+
results[:ALL]
|
68
|
+
else
|
69
|
+
results
|
62
70
|
end
|
63
71
|
end
|
72
|
+
end
|
64
73
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
else
|
69
|
-
results
|
74
|
+
if Gem::Version.new(ActiveRecord.version) >= Gem::Version.new('7.1.0')
|
75
|
+
def async_calculate_all(*expression_shortcuts, **named_expressions, &block)
|
76
|
+
async.calculate_all(*expression_shortcuts, **named_expressions, &block)
|
70
77
|
end
|
71
78
|
end
|
72
79
|
|
@@ -97,15 +104,22 @@ module CalculateAll
|
|
97
104
|
when /^(\w+)_minimum$/, /^minimum_(\w+)$/
|
98
105
|
"MIN(#{$1})"
|
99
106
|
else
|
100
|
-
raise ArgumentError, "Can't recognize expression shortcut #{
|
107
|
+
raise ArgumentError, "Can't recognize expression shortcut #{shortcut}"
|
101
108
|
end
|
102
109
|
end
|
103
110
|
end
|
104
111
|
|
105
112
|
module Querying
|
106
|
-
#
|
107
|
-
def calculate_all(*
|
108
|
-
all.calculate_all(*
|
113
|
+
# see CalculateAll#calculate_all
|
114
|
+
def calculate_all(*expression_shortcuts, **named_expressions, &block)
|
115
|
+
all.calculate_all(*expression_shortcuts, **named_expressions, &block)
|
116
|
+
end
|
117
|
+
|
118
|
+
if Gem::Version.new(ActiveRecord.version) >= Gem::Version.new('7.1.0')
|
119
|
+
# see CalculateAll#async_calculate_all
|
120
|
+
def async_calculate_all(*expression_shortcuts, **named_expressions, &block)
|
121
|
+
all.async_calculate_all(*expression_shortcuts, **named_expressions, &block)
|
122
|
+
end
|
109
123
|
end
|
110
124
|
end
|
111
125
|
end
|
@@ -117,9 +131,4 @@ ActiveSupport.on_load(:active_record) do
|
|
117
131
|
# Make the calculate_all method available for all ActiveRecord::Base classes
|
118
132
|
# You can for example call Orders.calculate_all(:count, :sum_cents)
|
119
133
|
ActiveRecord::Base.extend CalculateAll::Querying
|
120
|
-
|
121
|
-
# A hack for groupdate 3.0 since it checks if the calculate_all method is defined
|
122
|
-
# on the ActiveRecord::Calculations module. It is never called but it is just
|
123
|
-
# needed for the check.
|
124
|
-
ActiveRecord::Calculations.include CalculateAll::Querying
|
125
134
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: calculate-all
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexey Trofimenko
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-05-
|
10
|
+
date: 2025-05-19 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activesupport
|
@@ -42,11 +42,11 @@ files:
|
|
42
42
|
- calculate-all.gemspec
|
43
43
|
- gemfiles/activerecord42.gemfile
|
44
44
|
- gemfiles/activerecord50.gemfile
|
45
|
-
- gemfiles/activerecord51.gemfile
|
46
45
|
- gemfiles/activerecord52.gemfile
|
47
46
|
- gemfiles/activerecord60.gemfile
|
48
47
|
- gemfiles/activerecord61.gemfile
|
49
48
|
- gemfiles/activerecord70.gemfile
|
49
|
+
- gemfiles/activerecord71.gemfile
|
50
50
|
- lib/calculate-all.rb
|
51
51
|
- lib/calculate-all/version.rb
|
52
52
|
homepage: http://github.com/codesnik/calculate-all
|
@@ -60,7 +60,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
60
|
requirements:
|
61
61
|
- - ">="
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version: 2.
|
63
|
+
version: 2.6.0
|
64
64
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|