active_windows 0.1.2 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 997432adcf9a7b09e16440849eac9c1e2c5f740993d3a61e037bf425bd649be7
4
- data.tar.gz: 13a7e599db4e091490e1597892400838e205548a2c750b3e0307dbc46b7ddf1f
3
+ metadata.gz: 40d81b47d0bf35406b6d9004f3eb82b0137b3bfc0dc5bed7e2d33b697753aeae
4
+ data.tar.gz: 11897b5353a3c8b993922c7af8a4819964d417640bce9501182b145750051144
5
5
  SHA512:
6
- metadata.gz: 28680757fe01be911b248555904c9569576b39e5fd743dc715e710ef1cf79609a82b543ca25ed975e6d3bc85157c15795df915d5732ad44aa799bf466a55b8bb
7
- data.tar.gz: 7db21e9e9f36be6fd1f2db12e5fb3e999e1fa5dd588b8b5567e0fd3a19d21c8c5d4805ab21ee19e5747dc6cea3c8ecccfdce55fccab595bf339b494ddbe33326
6
+ metadata.gz: 3b858116cd6290e77e23c5688336102efd83a234bb2caca193728e9b9458a91591fe0653420f5cc400238603921f86e472d3d92ab4028342e4ad9eb2396b4d2e
7
+ data.tar.gz: 3974f6c3a73e2efbbb6aeb603c2ab4ddfd65a88eb23d10353fe94ef50746f38fc4c5c7abceec862f2eb53e8b26f710d3a7b9e274f3889132d83c48874d984e3d
data/README.md CHANGED
@@ -4,7 +4,7 @@ A Ruby DSL for SQL window functions in ActiveRecord. Write expressive window fun
4
4
 
5
5
  ```ruby
6
6
  # Fluent API
7
- User.row_number.partition_by(:department).order(:salary).as(:rank)
7
+ User.row_number.partition_by(:department).window_order(:salary).as(:rank)
8
8
 
9
9
  # Hash API
10
10
  User.window(row_number: { partition: :department, order: :salary, as: :rank })
@@ -41,17 +41,25 @@ ActiveWindows provides two equivalent APIs: a **fluent API** with chainable meth
41
41
 
42
42
  ### Fluent API
43
43
 
44
- Every window function method returns a chainable object with `.partition_by()`, `.order()`, and `.as()`:
44
+ Every window function method returns a chainable object with `.partition_by()`, `.window_order()`, and `.as()`:
45
45
 
46
46
  ```ruby
47
- User.row_number.partition_by(:department).order(:salary).as(:rank)
47
+ User.row_number.partition_by(:department).window_order(:salary).as(:rank)
48
48
  ```
49
49
 
50
- All three chain methods are optional. Order can be mixed freely:
50
+ All three chain methods are optional. The fluent API uses `.window_order()` (not `.order()`) to avoid collision with ActiveRecord's `.order()`, which controls the query-level `ORDER BY`. This lets you use both together:
51
51
 
52
52
  ```ruby
53
- User.row_number.as(:rn).order(:created_at)
54
- User.dense_rank.order(:score).as(:position)
53
+ User.row_number.window_order(:salary).as(:rn).order(:name)
54
+ # Window: OVER (ORDER BY salary)
55
+ # Query: ORDER BY name
56
+ ```
57
+
58
+ Order can be mixed freely:
59
+
60
+ ```ruby
61
+ User.row_number.as(:rn).window_order(:created_at)
62
+ User.dense_rank.window_order(:score).as(:position)
55
63
  ```
56
64
 
57
65
  ### Hash API
@@ -64,6 +72,8 @@ User.window(
64
72
  )
65
73
  ```
66
74
 
75
+ The hash API uses `order:` as a key (not a method call), so there's no naming conflict with ActiveRecord's `.order()`.
76
+
67
77
  Available options:
68
78
 
69
79
  | Option | Type | Description |
@@ -82,7 +92,7 @@ Window functions integrate naturally with standard ActiveRecord methods:
82
92
  User.where(active: true)
83
93
  .row_number
84
94
  .partition_by(:department)
85
- .order(:salary)
95
+ .window_order(:salary)
86
96
  .as(:rank)
87
97
 
88
98
  User.select(:name, :salary)
@@ -100,7 +110,7 @@ When no `.select()` is specified, `*` is automatically included so all model col
100
110
  Window function values are accessible as attributes on the returned records:
101
111
 
102
112
  ```ruby
103
- results = User.row_number.partition_by(:department).order(:salary).as(:rank)
113
+ results = User.row_number.partition_by(:department).window_order(:salary).as(:rank)
104
114
 
105
115
  results.each do |user|
106
116
  puts "#{user.name}: rank #{user.attributes['rank']}"
@@ -113,44 +123,44 @@ end
113
123
 
114
124
  ```ruby
115
125
  # ROW_NUMBER() - sequential integer within partition
116
- User.row_number.partition_by(:department).order(:salary).as(:rn)
126
+ User.row_number.partition_by(:department).window_order(:salary).as(:rn)
117
127
 
118
128
  # RANK() - rank with gaps for ties
119
- User.rank.partition_by(:department).order(:salary).as(:salary_rank)
129
+ User.rank.partition_by(:department).window_order(:salary).as(:salary_rank)
120
130
 
121
131
  # DENSE_RANK() - rank without gaps for ties
122
- User.dense_rank.partition_by(:department).order(:salary).as(:dense_salary_rank)
132
+ User.dense_rank.partition_by(:department).window_order(:salary).as(:dense_salary_rank)
123
133
 
124
134
  # PERCENT_RANK() - relative rank as a fraction (0 to 1)
125
- User.percent_rank.order(:salary).as(:percentile)
135
+ User.percent_rank.window_order(:salary).as(:percentile)
126
136
 
127
137
  # CUME_DIST() - cumulative distribution (fraction of rows <= current row)
128
- User.cume_dist.order(:salary).as(:cumulative)
138
+ User.cume_dist.window_order(:salary).as(:cumulative)
129
139
 
130
140
  # NTILE(n) - divide rows into n roughly equal buckets
131
- User.ntile(4).order(:salary).as(:quartile)
141
+ User.ntile(4).window_order(:salary).as(:quartile)
132
142
  ```
133
143
 
134
144
  ### Value Functions
135
145
 
136
146
  ```ruby
137
147
  # LAG(column, offset, default) - value from a preceding row
138
- User.lag(:salary).order(:hire_date).as(:prev_salary)
139
- User.lag(:salary, 2).order(:hire_date).as(:two_back) # custom offset
140
- User.lag(:salary, 1, 0).order(:hire_date).as(:prev_or_zero) # with default
148
+ User.lag(:salary).window_order(:hire_date).as(:prev_salary)
149
+ User.lag(:salary, 2).window_order(:hire_date).as(:two_back) # custom offset
150
+ User.lag(:salary, 1, 0).window_order(:hire_date).as(:prev_or_zero) # with default
141
151
 
142
152
  # LEAD(column, offset, default) - value from a following row
143
- User.lead(:salary).order(:hire_date).as(:next_salary)
144
- User.lead(:salary, 2, 0).order(:hire_date).as(:two_ahead)
153
+ User.lead(:salary).window_order(:hire_date).as(:next_salary)
154
+ User.lead(:salary, 2, 0).window_order(:hire_date).as(:two_ahead)
145
155
 
146
156
  # FIRST_VALUE(column) - first value in the window frame
147
- User.first_value(:name).partition_by(:department).order(:salary).as(:lowest_paid)
157
+ User.first_value(:name).partition_by(:department).window_order(:salary).as(:lowest_paid)
148
158
 
149
159
  # LAST_VALUE(column) - last value in the window frame
150
- User.last_value(:name).partition_by(:department).order(:salary).as(:highest_paid)
160
+ User.last_value(:name).partition_by(:department).window_order(:salary).as(:highest_paid)
151
161
 
152
162
  # NTH_VALUE(column, n) - nth value in the window frame
153
- User.nth_value(:name, 2).partition_by(:department).order(:salary).as(:second_lowest)
163
+ User.nth_value(:name, 2).partition_by(:department).window_order(:salary).as(:second_lowest)
154
164
  ```
155
165
 
156
166
  ### Aggregate Window Functions
@@ -196,7 +206,7 @@ User.window(sum: {
196
206
  ```ruby
197
207
  User.rank
198
208
  .partition_by(:department)
199
- .order(:salary)
209
+ .window_order(:salary)
200
210
  .as(:salary_rank)
201
211
  ```
202
212
 
@@ -227,14 +237,14 @@ end
227
237
  ```ruby
228
238
  User.lag(:name)
229
239
  .partition_by(:department)
230
- .order(:hire_date)
240
+ .window_order(:hire_date)
231
241
  .as(:previous_hire)
232
242
  ```
233
243
 
234
244
  ### Divide employees into salary quartiles
235
245
 
236
246
  ```ruby
237
- User.ntile(4).order(:salary).as(:quartile)
247
+ User.ntile(4).window_order(:salary).as(:quartile)
238
248
  ```
239
249
 
240
250
  ### Rank users by total order amount (with joins)
@@ -253,7 +263,7 @@ User.joins(:orders)
253
263
  Order.joins(:user)
254
264
  .row_number
255
265
  .partition_by("users.id")
256
- .order(amount: :desc)
266
+ .window_order(amount: :desc)
257
267
  .as(:order_rank)
258
268
 
259
269
  # Number each user's orders chronologically
@@ -261,7 +271,7 @@ Order.joins(:user)
261
271
  .select("orders.*, users.name AS user_name")
262
272
  .row_number
263
273
  .partition_by(:user_id)
264
- .order(:created_at)
274
+ .window_order(:created_at)
265
275
  .as(:order_number)
266
276
  ```
267
277
 
@@ -25,7 +25,7 @@ module ActiveWindows
25
25
  self
26
26
  end
27
27
 
28
- def order(*columns)
28
+ def window_order(*columns)
29
29
  @order_columns = columns.flatten
30
30
  self
31
31
  end
@@ -46,7 +46,7 @@ module ActiveWindows
46
46
 
47
47
  # Delegate common relation/query methods so the chain is transparent
48
48
  delegate :to_sql, :to_a, :to_ary, :load, :loaded?, :each, :map, :first, :last, :count,
49
- :where, :select, :joins, :group, :having, :limit, :offset, :reorder, :pluck,
49
+ :where, :select, :joins, :group, :having, :order, :limit, :offset, :reorder, :pluck,
50
50
  :find_each, :find_in_batches, :inspect, :exists?, :any?, :none?, :empty?,
51
51
  to: :to_relation
52
52
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveWindows
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_windows
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Andriichuk