activerecord-summarize 0.2.2 → 0.3.1
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 +21 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +11 -8
- data/activerecord-summarize.gemspec +1 -1
- data/docs/use_case_moderator_dashboard.md +2 -0
- data/lib/activerecord/summarize/version.rb +1 -1
- data/lib/activerecord/summarize.rb +17 -8
- data/lib/chainable_result.rb +7 -1
- metadata +2 -3
- data/.standard.yml +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d491ee7730156f77105ec7df6bac79b6410fa97b3387816f103dee233b43df8d
|
4
|
+
data.tar.gz: f33be41270ab955fcf2bf9b227cc7c212ae3aa385786da3a5b424a3e1e707e47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d1f5308da4fc8b781e8dd5a69a10c671106acdb9b565c056d23b33114fc96f8d4ff9a60f3887b861f9142b2564c3f33f17d9e8ea52c212c27abbdacc861e2d6
|
7
|
+
data.tar.gz: 318bad930ac53001068e6e3396d921f71ff62c8a0899abe7a96a55d9dab59aa7e6e45c2cdc6e7a5f5ae60b67f18a47be79cb73ee29a85e1fac25a988c43dc47a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
## [0.3.1] - 2022-06-23
|
2
|
+
|
3
|
+
- **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.
|
4
|
+
- **IMPROVEMENT:** Automated tests covering every `with` invocation style I can think of for both implementations and a number of new tests to confirm that `noop: true` produces the same results as (default) `noop: false`.
|
5
|
+
- **IMPROVEMENT:** After the initial release I forgot I had a CHANGELOG, and now I've back-filled it.
|
6
|
+
|
7
|
+
## [0.3.0] - 2022-06-04
|
8
|
+
|
9
|
+
- **BUGFIX:** `.sum(:foo)` of no rows or of all-null values now returns 0 instead of failing (completing partial fix from 0.2.3)
|
10
|
+
- **BREAKING:** extremely unlikely to actually break anything, but `.count("distinct id")` now raises as `.distinct.count(:id)` already did. (AFAICT, by the nature of the techniques underlying `summarize`, `distinct` counts cannot be supported.)
|
11
|
+
- **IMPROVEMENT:** Automated tests covering basic functions and past problem areas.
|
12
|
+
|
13
|
+
## [0.2.3] - 2022-05-01
|
14
|
+
|
15
|
+
- **BUGFIX:** Fix results for SQL `SUM(null)` (n.b., this turned out to be only a partial fix)
|
16
|
+
- **BUGFIX:** Support summarize with only one query (not often very useful, but it should work!)
|
17
|
+
|
18
|
+
## [0.2.2] - 2022-04-29
|
19
|
+
|
20
|
+
- **BUGFIX:** Incorrect Arel generation when using `.where(*anything).sum` inside a `summarize` block
|
21
|
+
|
1
22
|
## [0.2.1] - 2022-02-17
|
2
23
|
|
3
24
|
- Initial public release
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
activerecord-summarize (0.
|
4
|
+
activerecord-summarize (0.3.1)
|
5
5
|
activerecord (>= 5.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (7.0.
|
11
|
-
activesupport (= 7.0.
|
12
|
-
activerecord (7.0.
|
13
|
-
activemodel (= 7.0.
|
14
|
-
activesupport (= 7.0.
|
15
|
-
activesupport (7.0.
|
10
|
+
activemodel (7.0.3)
|
11
|
+
activesupport (= 7.0.3)
|
12
|
+
activerecord (7.0.3)
|
13
|
+
activemodel (= 7.0.3)
|
14
|
+
activesupport (= 7.0.3)
|
15
|
+
activesupport (7.0.3)
|
16
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
17
|
i18n (>= 1.6, < 2)
|
18
18
|
minitest (>= 5.1)
|
19
19
|
tzinfo (~> 2.0)
|
20
20
|
ast (2.4.2)
|
21
|
-
concurrent-ruby (1.1.
|
21
|
+
concurrent-ruby (1.1.10)
|
22
22
|
i18n (1.10.0)
|
23
23
|
concurrent-ruby (~> 1.0)
|
24
24
|
minitest (5.15.0)
|
@@ -44,6 +44,7 @@ GEM
|
|
44
44
|
rubocop (>= 1.7.0, < 2.0)
|
45
45
|
rubocop-ast (>= 0.4.0)
|
46
46
|
ruby-progressbar (1.11.0)
|
47
|
+
sqlite3 (1.4.2)
|
47
48
|
standard (1.7.2)
|
48
49
|
rubocop (= 1.25.1)
|
49
50
|
rubocop-performance (= 1.13.2)
|
@@ -56,9 +57,11 @@ PLATFORMS
|
|
56
57
|
x86_64-linux
|
57
58
|
|
58
59
|
DEPENDENCIES
|
60
|
+
activerecord (= 7.0.3)
|
59
61
|
activerecord-summarize!
|
60
62
|
minitest (~> 5.0)
|
61
63
|
rake (~> 13.0)
|
64
|
+
sqlite3 (= 1.4.2)
|
62
65
|
standard (~> 1.3)
|
63
66
|
|
64
67
|
BUNDLED WITH
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
25
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
26
|
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
-
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
27
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci|vscode|standard)|appveyor)})
|
28
28
|
end
|
29
29
|
end
|
30
30
|
spec.bindir = "exe"
|
@@ -11,6 +11,8 @@ For each subreddit that a user moderates, the user should see these stats with r
|
|
11
11
|
- count of how many posts from this period were buried, i.e., ended up with negative karma
|
12
12
|
- grouped by post creation date, the percentage of posts that ended up being popular, where popular means having a karma score greater than a per-subreddit-configured threshold
|
13
13
|
- grouped by post creation day of the week, the average number of comments per non-buried post
|
14
|
+
|
15
|
+
> *Below, grouping by day of the week is handled with `.group("EXTRACT(DOW FROM posts.created_at)")`*
|
14
16
|
|
15
17
|
## Background
|
16
18
|
|
@@ -38,7 +38,7 @@ module ActiveRecord::Summarize
|
|
38
38
|
|
39
39
|
def process(&block)
|
40
40
|
# For noop, just yield the original relation and a transparent `with` proc.
|
41
|
-
return yield(@relation,
|
41
|
+
return yield(@relation, ChainableResult::SYNC_WITH) if noop?
|
42
42
|
# Within the block, the relation and its future clones intercept calls to
|
43
43
|
# `count` and `sum`, registering them and returning a ChainableResult via
|
44
44
|
# summarize.add_calculation.
|
@@ -127,28 +127,36 @@ module ActiveRecord::Summarize
|
|
127
127
|
# grouped_query = groups.any? ? from_where.group(*groups) : from_where
|
128
128
|
grouped_query = groups.any? ? from_where.group(*1..groups.size) : from_where
|
129
129
|
data = grouped_query.pluck(*groups, *value_selects)
|
130
|
+
# .pluck(:one_column) returns an array of values instead of an array of arrays,
|
131
|
+
# which breaks the aggregation and assignment below in case anyone ever asks
|
132
|
+
# `summarize` for only one thing.
|
133
|
+
data = data.map { |d| [d] } if (groups.size + value_selects.size) == 1
|
130
134
|
|
131
135
|
# Aggregate & assign results
|
132
136
|
group_idx = groups.each_with_index.to_h
|
133
137
|
starting_values, reducers = @calculations.each_with_index.map do |f, i|
|
134
138
|
value_column = groups.size + i
|
135
139
|
group_columns = f.relation.group_values.map { |k| group_idx[k] }
|
140
|
+
# `row[value_column] || 0` pattern in reducers because SQL SUM(NULL)
|
141
|
+
# returns NULL, but like ActiveRecord we always want .sum to return a
|
142
|
+
# number, and our "starting_values and reducers" implementation means
|
143
|
+
# we sometimes will have to add NULL to our numbers.
|
136
144
|
case group_columns.size
|
137
145
|
when 0 then [
|
138
146
|
0,
|
139
|
-
->(memo, row) { memo + row[value_column] }
|
147
|
+
->(memo, row) { memo + (row[value_column] || 0) }
|
140
148
|
]
|
141
149
|
when 1 then [
|
142
150
|
Hash.new(0), # Default 0 makes the reducer much cleaner, but we have to clean it up later
|
143
151
|
->(memo, row) {
|
144
|
-
memo[row[group_columns[0]]] += row[value_column] unless row[value_column].zero?
|
152
|
+
memo[row[group_columns[0]]] += row[value_column] unless (row[value_column] || 0).zero?
|
145
153
|
memo
|
146
154
|
}
|
147
155
|
]
|
148
156
|
else [
|
149
157
|
Hash.new(0),
|
150
158
|
->(memo, row) {
|
151
|
-
memo[group_columns.map { |i| row[i] }] += row[value_column] unless row[value_column].zero?
|
159
|
+
memo[group_columns.map { |i| row[i] }] += row[value_column] unless (row[value_column] || 0).zero?
|
152
160
|
memo
|
153
161
|
}
|
154
162
|
]
|
@@ -233,14 +241,14 @@ module ActiveRecord::Summarize
|
|
233
241
|
def select_value(base_relation)
|
234
242
|
where = relation.where_clause - base_relation.where_clause
|
235
243
|
for_select = column
|
236
|
-
for_select = Arel::Nodes::Case.new(where.ast).when(true, for_select).else(
|
244
|
+
for_select = Arel::Nodes::Case.new(where.ast).when(true, for_select).else(unmatch_arel_node) unless where.empty?
|
237
245
|
function.new([for_select]).tap { |f| f.distinct = relation.distinct_value }
|
238
246
|
end
|
239
247
|
|
240
|
-
def
|
248
|
+
def unmatch_arel_node
|
241
249
|
case method
|
242
|
-
when "sum" then 0
|
243
|
-
when "count" then nil
|
250
|
+
when "sum" then 0 # Adding zero to a sum does nothing
|
251
|
+
when "count" then nil # In SQL, null is no value and is not counted
|
244
252
|
else raise "Unknown calculation method"
|
245
253
|
end
|
246
254
|
end
|
@@ -268,6 +276,7 @@ module ActiveRecord::Summarize
|
|
268
276
|
case operation = operation.to_s.downcase
|
269
277
|
when "count", "sum"
|
270
278
|
column_name = :id if [nil, "*", :all].include? column_name
|
279
|
+
raise Unsummarizable, "DISTINCT in SQL is not reliably correct with summarize" if column_name.is_a?(String) && /\bdistinct\b/i === column_name
|
271
280
|
@summarize.add_calculation(self, operation, aggregate_column(column_name))
|
272
281
|
else super
|
273
282
|
end
|
data/lib/chainable_result.rb
CHANGED
@@ -69,7 +69,7 @@ class ChainableResult
|
|
69
69
|
def self.wrap(v, method = nil, *args, **opts, &block)
|
70
70
|
method ||= block ? :then : :itself
|
71
71
|
klass = case v
|
72
|
-
when ChainableResult then
|
72
|
+
when ChainableResult then ChainableResult::Future
|
73
73
|
when ::Array then ChainableResult::Array
|
74
74
|
when ::Hash then ChainableResult::Hash
|
75
75
|
else ChainableResult::Other
|
@@ -81,7 +81,13 @@ class ChainableResult
|
|
81
81
|
ChainableResult.wrap(results.size == 1 ? results.first : results, :then, &block)
|
82
82
|
end
|
83
83
|
|
84
|
+
def self.sync_with(*results, &block)
|
85
|
+
# Non-time-traveling, synchronous version of `with` for testing
|
86
|
+
(results.size == 1 ? results.first : results).then(&block)
|
87
|
+
end
|
88
|
+
|
84
89
|
WITH = method(:with)
|
90
|
+
SYNC_WITH = method(:sync_with)
|
85
91
|
|
86
92
|
def self.resolve_item(item)
|
87
93
|
case item
|
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.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Paine
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -46,7 +46,6 @@ executables: []
|
|
46
46
|
extensions: []
|
47
47
|
extra_rdoc_files: []
|
48
48
|
files:
|
49
|
-
- ".standard.yml"
|
50
49
|
- CHANGELOG.md
|
51
50
|
- Gemfile
|
52
51
|
- Gemfile.lock
|