activerecord-summarize 0.3.1 → 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/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/bin/console +3 -2
- data/lib/activerecord/summarize/version.rb +1 -1
- data/lib/activerecord/summarize.rb +40 -19
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e87406ae4f6c3aeec411824af98bb21b12457815f24e09f446ed8576bfc1352
|
4
|
+
data.tar.gz: 266e9065f9e49e458fa427d3a82c55eae82eafd983c27a5c8729125c2c75eb57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b515f889c18886602e6a695982ad755bdbb44b38ec93a1422cc92fb0ae9da612e3a7f2854d12c07864bedde52134f121823da201a8f52383564643ca2a7b8e84
|
7
|
+
data.tar.gz: e67437ac8503938c9f6f671fcf7c952aed1998efb045ff6a05a7d2a16e2c50ed7d79519f93b8a0d196cbf7d9d1bd9c23673e51487f7be8de3554cb27dde264fc
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## [0.4.0] - 2023-02-27
|
2
|
+
|
3
|
+
- **FEATURE:** Support for top-level .group(:belongs_to_association), returning hash with models as keys.
|
4
|
+
|
5
|
+
I didn't realize this until a few months ago, but in ActiveRecord, if `Foo belongs_to :bar`, you can do `Foo.group(:bar).count` and get back a hash with `Bar` records as keys and counts as values. (ActiveRecord executes two total queries to implement this, one for the counts grouped by the `bar_id` foreign key, then another to retrieve the `Bar` models.)
|
6
|
+
|
7
|
+
Now the same behavior works with `summarize`: you can still retrieve any number of counts and/or sums about `Foo`—including some with additional filters and even sub-grouping—in a single query, and then we'll execute one additional query to retrieve the records for the `Bar` model keys.
|
8
|
+
|
9
|
+
- **IMPROVEMENT:** `bin/console` is now much more useful for developing `activerecord-summarize`
|
10
|
+
|
11
|
+
- **IMPROVEMENT:** Added some tests for queries joining HABTM associations and (of course, supporting the new feature) `belongs_to` associations. `summarize` preceded by joins is already stable and documented, but it didn't have tests before.
|
12
|
+
|
1
13
|
## [0.3.1] - 2022-06-23
|
2
14
|
|
3
15
|
- **BUGFIX:** `with` didn't work correctly with a single argument. Embarassingly, both the time-traveling version of `with` and the trivial/fake one provided when `noop: true` is set had single argument bugs, and they were different bugs.
|
data/Gemfile.lock
CHANGED
data/bin/console
CHANGED
@@ -2,10 +2,11 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "bundler/setup"
|
5
|
+
require "active_record"
|
5
6
|
require "activerecord/summarize"
|
7
|
+
require_relative "../test/test_data" # Test fixtures so there's something to play with
|
6
8
|
|
7
|
-
# You can
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
# You can use a different console, if you like.
|
9
10
|
|
10
11
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
12
|
# require "pry"
|
@@ -7,7 +7,7 @@ module ActiveRecord::Summarize
|
|
7
7
|
class Unsummarizable < StandardError; end
|
8
8
|
|
9
9
|
class Summarize
|
10
|
-
attr_reader :current_result_row, :pure, :noop, :from_where
|
10
|
+
attr_reader :current_result_row, :base_groups, :base_association, :pure, :noop, :from_where
|
11
11
|
alias_method :pure?, :pure
|
12
12
|
alias_method :noop?, :noop
|
13
13
|
|
@@ -29,7 +29,18 @@ module ActiveRecord::Summarize
|
|
29
29
|
def initialize(relation, pure: nil, noop: false)
|
30
30
|
@relation = relation
|
31
31
|
@noop = noop
|
32
|
-
|
32
|
+
@base_groups, @base_association = relation.group_values.dup.then do |group_fields|
|
33
|
+
# Based upon a bit from ActiveRecord::Calculations.execute_grouped_calculation,
|
34
|
+
# if the base relation is grouped only by a belongs_to association, group by
|
35
|
+
# the association's foreign key.
|
36
|
+
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
37
|
+
association = relation.klass._reflect_on_association(group_fields.first)
|
38
|
+
# Like ActiveRecord's group(:association).count behavior, this only works with belongs_to associations
|
39
|
+
next [Array(association.foreign_key), association] if association&.belongs_to?
|
40
|
+
end
|
41
|
+
[group_fields, nil]
|
42
|
+
end
|
43
|
+
has_base_groups = base_groups.any?
|
33
44
|
raise Unsummarizable, "`summarize` must be pure when called on a grouped relation" if pure == false && has_base_groups
|
34
45
|
raise ArgumentError, "`summarize(noop: true)` is impossible on a grouped relation" if noop && has_base_groups
|
35
46
|
@pure = has_base_groups || !!pure
|
@@ -53,24 +64,38 @@ module ActiveRecord::Summarize
|
|
53
64
|
))
|
54
65
|
ChainableResult.with_cache(!pure?) do
|
55
66
|
# `resolve` builds the single query that answers all collected calculations,
|
56
|
-
# executes it, and aggregates the results by the values of
|
57
|
-
#
|
58
|
-
#
|
67
|
+
# executes it, and aggregates the results by the values of `base_groups`.
|
68
|
+
# In the common case of no `base_groups`, the resolve returns:
|
69
|
+
# `{[]=>[*final_value_for_each_calculation]}`
|
59
70
|
result = resolve.transform_values! do |row|
|
60
71
|
# Each row (in the common case, only one) is used to resolve any
|
61
72
|
# ChainableResults returned by the block. These may be a one-to-one mapping,
|
62
|
-
# or the block return may have combined some results via `with
|
73
|
+
# or the block return may have combined some results via `with`, chained
|
63
74
|
# additional methods on results, etc..
|
64
75
|
@current_result_row = row
|
65
76
|
future_block_result.value
|
66
77
|
end.then do |result|
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
78
|
+
# Now unpack/fix-up the result keys to match shape of Relation.count or Relation.group(*cols).count return values
|
79
|
+
if base_groups.empty?
|
80
|
+
# Change ungrouped result from `{[]=>v}` to `v`, like Relation.count
|
81
|
+
result.values.first
|
82
|
+
elsif base_association
|
83
|
+
# Change grouped-by-one-belongs_to-association result from `{[id1]=>v1,[id2]=>v2,...}` to
|
84
|
+
# `{<AssociatedModel id:id1>=>v1,<AssociatedModel id:id2>=>v2,...}` like Relation.group(:association).count
|
85
|
+
|
86
|
+
# Loosely based on a bit from ActiveRecord::Calculations.execute_grouped_calculation,
|
87
|
+
# retrieve the records for the group association and replace the keys of our final result.
|
88
|
+
key_class = base_association.klass.base_class
|
89
|
+
key_records = key_class
|
90
|
+
.where(key_class.primary_key => result.keys.flatten)
|
91
|
+
.index_by(&:id)
|
92
|
+
result.transform_keys! { |k| key_records[k[0]] }
|
93
|
+
elsif base_groups.size == 1
|
94
|
+
# Change grouped-by-one-column result from `{[k1]=>v1,[k2]=>v2,...}` to `{k1=>v1,k2=>v2,...}`, like Relation.group(:column).count
|
95
|
+
result.transform_keys! { |k| k[0] }
|
96
|
+
else
|
97
|
+
# Multiple-column base grouping (though perhaps relatively rare) requires no change.
|
98
|
+
result
|
74
99
|
end
|
75
100
|
end
|
76
101
|
if !pure?
|
@@ -166,7 +191,7 @@ module ActiveRecord::Summarize
|
|
166
191
|
base_group_columns = (0...base_groups.size)
|
167
192
|
data
|
168
193
|
.group_by { |row| row[base_group_columns] }
|
169
|
-
.tap { |h| h[[]] = [] if h.empty? && base_groups.
|
194
|
+
.tap { |h| h[[]] = [] if h.empty? && base_groups.empty? }
|
170
195
|
.transform_values! do |rows|
|
171
196
|
values = starting_values.map(&:dup) # map(&:dup) since some are hashes and we don't want to mutate starting_values
|
172
197
|
rows.each do |row|
|
@@ -201,14 +226,10 @@ module ActiveRecord::Summarize
|
|
201
226
|
end
|
202
227
|
end
|
203
228
|
|
204
|
-
def base_groups
|
205
|
-
@relation.group_values.dup
|
206
|
-
end
|
207
|
-
|
208
229
|
def all_groups
|
209
230
|
# keep all base groups, even if they did something silly like group by
|
210
231
|
# the same key twice, but otherwise don't repeat any groups
|
211
|
-
groups = base_groups
|
232
|
+
groups = base_groups.dup
|
212
233
|
groups_set = Set.new(groups)
|
213
234
|
@calculations.map { |f| f.relation.group_values }.flatten.each do |k|
|
214
235
|
next if groups_set.include? k
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-summarize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Paine
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|