active_windows 0.1.4 → 0.1.6
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/README.md +13 -0
- data/docs/REVIEW_AND_PLAN.md +2 -0
- data/lib/active_windows/active_record_extensions.rb +34 -2
- data/lib/active_windows/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b393a3c07582141b6656bbf2f9ffe55193f6fd32c3505af7875a9e6f76fec7d0
|
|
4
|
+
data.tar.gz: 7571dfc93bc77a96e495b2c74799100320e7bf6666809ceb754ec5fc64efffc9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8380c1b66b1a1a3c31f9e9c5a9035c43ff7689e499254b4bd7d9b250fbfaeb36054c8a012e49f4f84756a0a6ab8c42bf3a931bad82fda768b07556f955bdac3a
|
|
7
|
+
data.tar.gz: 92128ba7625b79177654e2f09a76f163d5901ddf2105f84174fe591d81dd020d276fdddb428d76649c0648ac7127f2bfcbee4b539c007221c74be1c6d231ae85
|
data/README.md
CHANGED
|
@@ -84,6 +84,19 @@ Available options:
|
|
|
84
84
|
| `:frame` | `String` | Raw SQL frame clause (e.g. `"ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"`) |
|
|
85
85
|
| `:value` | `Symbol`, `String`, `Array` | Expression(s) passed as function arguments |
|
|
86
86
|
|
|
87
|
+
### Association Names
|
|
88
|
+
|
|
89
|
+
You can use `belongs_to` association names instead of foreign key columns. ActiveWindows automatically resolves them:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# These are equivalent:
|
|
93
|
+
Order.row_number.partition_by(:user).window_order(:amount).as(:rn)
|
|
94
|
+
Order.row_number.partition_by(:user_id).window_order(:amount).as(:rn)
|
|
95
|
+
|
|
96
|
+
# Works in the hash API too:
|
|
97
|
+
Order.window(row_number: { partition: :user, order: :amount, as: :rn })
|
|
98
|
+
```
|
|
99
|
+
|
|
87
100
|
### Chaining with ActiveRecord
|
|
88
101
|
|
|
89
102
|
Window functions integrate naturally with standard ActiveRecord methods:
|
data/docs/REVIEW_AND_PLAN.md
CHANGED
|
@@ -34,6 +34,8 @@ The gem provides a fluent DSL for SQL window functions in ActiveRecord. Core fun
|
|
|
34
34
|
- ~~**PostgreSQL CI**~~ — Added. GitHub Actions workflow tests against PostgreSQL 17 with service container.
|
|
35
35
|
- ~~**MySQL CI**~~ — Added. GitHub Actions workflow tests against MySQL 8.0 with service container.
|
|
36
36
|
- ~~**MySQL compatibility**~~ — Fixed. Aliases now use `klass.connection.quote_column_name` to properly quote reserved words (e.g., `rank`) with backticks on MySQL and double quotes on PostgreSQL/SQLite. Test assertions use adapter-agnostic `q()` and `col()` helpers.
|
|
37
|
+
- ~~**WindowChain `order` naming collision**~~ — Fixed. Renamed to `window_order` to avoid conflict with ActiveRecord's `.order()`. WindowChain now delegates `.order()` to the relation for query-level ordering. Uses `method_missing` for full relation method coverage.
|
|
38
|
+
- ~~**Association name resolution**~~ — Added. `partition_by(:user)` automatically resolves to `user_id` via `belongs_to` reflection. Works in both fluent and hash APIs. 74 tests, 355 assertions.
|
|
37
39
|
|
|
38
40
|
---
|
|
39
41
|
|
|
@@ -72,9 +72,18 @@ module ActiveWindows
|
|
|
72
72
|
raise ArgumentError, "wrong number of arguments (given 0, expected 1+)" if args.empty?
|
|
73
73
|
|
|
74
74
|
processed = process_window_args(args)
|
|
75
|
-
arel_nodes = processed.map { |name, options| build_window_function(name, options || {}) }
|
|
76
75
|
|
|
77
76
|
result = spawn
|
|
77
|
+
|
|
78
|
+
# Auto-join has_one associations referenced in partition/order
|
|
79
|
+
joins_needed = processed.flat_map do |_name, options|
|
|
80
|
+
next [] unless options.is_a?(Hash)
|
|
81
|
+
association_joins_for(options[:partition]) + association_joins_for(options[:order])
|
|
82
|
+
end.uniq
|
|
83
|
+
result = result.joins(*joins_needed) if joins_needed.any?
|
|
84
|
+
|
|
85
|
+
arel_nodes = processed.map { |name, options| build_window_function(name, options || {}) }
|
|
86
|
+
|
|
78
87
|
# Ensure we keep all columns alongside the window function columns
|
|
79
88
|
result = result.select(klass.arel_table[Arel.star]) if result.select_values.empty?
|
|
80
89
|
result.select(*arel_nodes)
|
|
@@ -217,7 +226,30 @@ module ActiveWindows
|
|
|
217
226
|
if name.is_a?(Arel::Nodes::Node) || name.is_a?(Arel::Nodes::SqlLiteral)
|
|
218
227
|
name
|
|
219
228
|
else
|
|
220
|
-
|
|
229
|
+
resolve_column(name)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def resolve_column(name)
|
|
234
|
+
name_sym = name.to_sym
|
|
235
|
+
return klass.arel_table[name_sym] if klass.column_names.include?(name.to_s)
|
|
236
|
+
|
|
237
|
+
reflection = klass.reflect_on_association(name_sym)
|
|
238
|
+
if reflection&.macro == :belongs_to
|
|
239
|
+
klass.arel_table[reflection.foreign_key.to_sym]
|
|
240
|
+
elsif reflection&.macro == :has_one
|
|
241
|
+
reflection.klass.arel_table[reflection.klass.primary_key.to_sym]
|
|
242
|
+
else
|
|
243
|
+
klass.arel_table[name_sym]
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def association_joins_for(columns)
|
|
248
|
+
Array(columns).filter_map do |col|
|
|
249
|
+
next unless col.is_a?(Symbol) || col.is_a?(String)
|
|
250
|
+
|
|
251
|
+
reflection = klass.reflect_on_association(col.to_sym)
|
|
252
|
+
col.to_sym if reflection&.macro == :has_one
|
|
221
253
|
end
|
|
222
254
|
end
|
|
223
255
|
|