ranked-model 0.4.9 → 0.4.10
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/ci.yml +26 -9
- data/CHANGELOG.md +5 -0
- data/Readme.mkd +24 -2
- data/lib/ranked-model/ranker.rb +12 -2
- data/lib/ranked-model/version.rb +1 -1
- data/spec/notifications_spec.rb +89 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 329351cc90908d43dfeda2987183ebedff1abe8cbefc05afe1d0bd6760df2c41
|
|
4
|
+
data.tar.gz: f99a8987935928d33ac77555ff9bd5614002db38fffc7cd1a6c85449e236a42b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b31ddb712c2425d6a59c918ab506f6e2855f9a40575cc298df8d1271f701741a5bfa88040410299226c46ace4cec00459a1f5d804b303a0326de428c1035cd1
|
|
7
|
+
data.tar.gz: 02ef12162bec60ee6b82f4f8dd0200ed82926bed9d4a6e5e4e77ce6d8c46ec7d2343d347ecf6e6a234cadec2e3e42042f7097bf9e7791fe2ab75342ac89c2777
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -10,12 +10,13 @@ jobs:
|
|
|
10
10
|
fail-fast: false
|
|
11
11
|
matrix:
|
|
12
12
|
ruby:
|
|
13
|
-
- 2.4
|
|
14
13
|
- 2.5
|
|
15
14
|
- 2.6
|
|
16
15
|
- 2.7
|
|
17
16
|
- '3.0'
|
|
18
17
|
- 3.1
|
|
18
|
+
- 3.2
|
|
19
|
+
- 3.3
|
|
19
20
|
gemfile:
|
|
20
21
|
- gemfiles/rails_5_2.gemfile
|
|
21
22
|
- gemfiles/rails_6_0.gemfile
|
|
@@ -26,12 +27,6 @@ jobs:
|
|
|
26
27
|
- mysql
|
|
27
28
|
- postgresql
|
|
28
29
|
exclude:
|
|
29
|
-
- ruby: 2.4
|
|
30
|
-
gemfile: gemfiles/rails_6_0.gemfile
|
|
31
|
-
- ruby: 2.4
|
|
32
|
-
gemfile: gemfiles/rails_6_1.gemfile
|
|
33
|
-
- ruby: 2.4
|
|
34
|
-
gemfile: gemfiles/rails_7_0.gemfile
|
|
35
30
|
- ruby: 2.5
|
|
36
31
|
gemfile: gemfiles/rails_7_0.gemfile
|
|
37
32
|
- ruby: 2.6
|
|
@@ -40,8 +35,30 @@ jobs:
|
|
|
40
35
|
gemfile: gemfiles/rails_5_2.gemfile
|
|
41
36
|
- ruby: 3.1
|
|
42
37
|
gemfile: gemfiles/rails_5_2.gemfile
|
|
43
|
-
- ruby: 3.
|
|
38
|
+
- ruby: 3.2
|
|
39
|
+
gemfile: gemfiles/rails_4_2.gemfile
|
|
40
|
+
- ruby: 3.2
|
|
41
|
+
gemfile: gemfiles/rails_5_0.gemfile
|
|
42
|
+
- ruby: 3.2
|
|
43
|
+
gemfile: gemfiles/rails_5_1.gemfile
|
|
44
|
+
- ruby: 3.2
|
|
45
|
+
gemfile: gemfiles/rails_5_2.gemfile
|
|
46
|
+
- ruby: 3.2
|
|
44
47
|
gemfile: gemfiles/rails_6_0.gemfile
|
|
48
|
+
- ruby: 3.2
|
|
49
|
+
gemfile: gemfiles/rails_6_1.gemfile
|
|
50
|
+
- ruby: 3.3
|
|
51
|
+
gemfile: gemfiles/rails_4_2.gemfile
|
|
52
|
+
- ruby: 3.3
|
|
53
|
+
gemfile: gemfiles/rails_5_0.gemfile
|
|
54
|
+
- ruby: 3.3
|
|
55
|
+
gemfile: gemfiles/rails_5_1.gemfile
|
|
56
|
+
- ruby: 3.3
|
|
57
|
+
gemfile: gemfiles/rails_5_2.gemfile
|
|
58
|
+
- ruby: 3.3
|
|
59
|
+
gemfile: gemfiles/rails_6_0.gemfile
|
|
60
|
+
- ruby: 3.3
|
|
61
|
+
gemfile: gemfiles/rails_6_1.gemfile
|
|
45
62
|
os:
|
|
46
63
|
- ubuntu-latest
|
|
47
64
|
services:
|
|
@@ -72,7 +89,7 @@ jobs:
|
|
|
72
89
|
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
|
73
90
|
DB: ${{ matrix.db }}
|
|
74
91
|
steps:
|
|
75
|
-
- uses: actions/checkout@
|
|
92
|
+
- uses: actions/checkout@v4
|
|
76
93
|
- name: Set up Ruby
|
|
77
94
|
uses: ruby/setup-ruby@v1
|
|
78
95
|
with:
|
data/CHANGELOG.md
ADDED
data/Readme.mkd
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
**ranked-model** is a modern row sorting library built for Rails 4.2+. It uses ARel aggressively and is better optimized than most other libraries.
|
|
2
2
|
|
|
3
|
-
[](https://github.com/brendon/ranked-model/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
ANNOUNCING: Positioning, the gem
|
|
6
|
+
--------------------------------
|
|
7
|
+
|
|
8
|
+
As maintainer of both Acts As List and the Ranked Model gems, I've become intimately aquainted with the strengths and weaknesses of each. I ended up writing a small scale Rails Concern for positioning database rows for a recent project and it worked really well so I've decided to release it as a gem: [Positioning](https://github.com/brendon/positioning)
|
|
9
|
+
|
|
10
|
+
Positioning works similarly to Acts As List in that it maintains a sequential list of integer values as positions. It differs in that it encourages a unique constraints on the position column and supports multiple lists per database table. It borrows Ranked Model's concept of relative positioning. I encourage you to check it out and give it a whirl on your project!
|
|
4
11
|
|
|
5
12
|
Installation
|
|
6
13
|
------------
|
|
@@ -151,7 +158,7 @@ ducks.map do |duck|
|
|
|
151
158
|
end
|
|
152
159
|
```
|
|
153
160
|
|
|
154
|
-
Every call to `duck.row_order_rank` will make a call to the DB to check the rank of that
|
|
161
|
+
Every call to `duck.row_order_rank` will make a call to the DB to check the rank of that
|
|
155
162
|
particular element. If you have a long list of elements this might cause issues to your DB.
|
|
156
163
|
|
|
157
164
|
In order to avoid that, you can use the `rank(:your_rank)` scope and some Ruby code to get
|
|
@@ -262,6 +269,21 @@ this occurs, ranked-model will try to shift other records out of the way. If ite
|
|
|
262
269
|
shifted anymore, it will rebalance the distribution of rank numbers across all members
|
|
263
270
|
of the ranked group.
|
|
264
271
|
|
|
272
|
+
Record updates to rebalance ranks do not trigger ActiveRecord callbacks. If you need to react to these updates
|
|
273
|
+
(to index them in a secondary data store, for example), you can subscribe to the `ranked_model.ranks_updated`
|
|
274
|
+
[ActiveSupport notification](https://api.rubyonrails.org/v7.1/classes/ActiveSupport/Notifications.html).
|
|
275
|
+
Subscribed consumers receive an event for each rearrangement or rebalancing, the payload of which includes the
|
|
276
|
+
triggering instance and the `scope` and `with_same` options for the ranking, which can be used to retrieve the
|
|
277
|
+
affected records.
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
ActiveSupport::Notifications.subscribe("ranked_model.ranks_updated") do |_name, _start, _finish, _id, payload|
|
|
281
|
+
# payload[:instance] - the instance whose update triggered the rebalance
|
|
282
|
+
# payload[:scope] - the scope applied to the ranking
|
|
283
|
+
# payload[:with_same] - the with_same option applied to the ranking
|
|
284
|
+
end
|
|
285
|
+
```
|
|
286
|
+
|
|
265
287
|
Contributing
|
|
266
288
|
------------
|
|
267
289
|
|
data/lib/ranked-model/ranker.rb
CHANGED
|
@@ -173,7 +173,7 @@ module RankedModel
|
|
|
173
173
|
|
|
174
174
|
def rank_at_average(min, max)
|
|
175
175
|
if (max - min).between?(-1, 1) # No room at the inn...
|
|
176
|
-
rebalance_ranks
|
|
176
|
+
notify_ranks_updated { rebalance_ranks }
|
|
177
177
|
position_at position
|
|
178
178
|
else
|
|
179
179
|
rank_at( ( ( max - min ).to_f / 2 ).ceil + min )
|
|
@@ -183,7 +183,7 @@ module RankedModel
|
|
|
183
183
|
def assure_unique_position
|
|
184
184
|
if ( new_record? || rank_changed? )
|
|
185
185
|
if (rank > RankedModel::MAX_RANK_VALUE) || rank_taken?
|
|
186
|
-
rearrange_ranks
|
|
186
|
+
notify_ranks_updated { rearrange_ranks }
|
|
187
187
|
end
|
|
188
188
|
end
|
|
189
189
|
end
|
|
@@ -350,6 +350,16 @@ module RankedModel
|
|
|
350
350
|
end
|
|
351
351
|
end
|
|
352
352
|
|
|
353
|
+
def notify_ranks_updated(&block)
|
|
354
|
+
ActiveSupport::Notifications.instrument(
|
|
355
|
+
"ranked_model.ranks_updated",
|
|
356
|
+
instance: instance,
|
|
357
|
+
scope: ranker.scope,
|
|
358
|
+
with_same: ranker.with_same
|
|
359
|
+
) do
|
|
360
|
+
block.call
|
|
361
|
+
end
|
|
362
|
+
end
|
|
353
363
|
end
|
|
354
364
|
|
|
355
365
|
end
|
data/lib/ranked-model/version.rb
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "notifications" do
|
|
4
|
+
before do
|
|
5
|
+
@notifications_count = 0
|
|
6
|
+
@notification_payloads = []
|
|
7
|
+
|
|
8
|
+
ActiveSupport::Notifications.subscribe("ranked_model.ranks_updated") do |name, start, finish, id, payload|
|
|
9
|
+
@notifications_count += 1
|
|
10
|
+
@notification_payloads << payload
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
after do
|
|
15
|
+
ActiveSupport::Notifications.unsubscribe("ranked_model.ranks_updated")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context "when rearranging" do
|
|
19
|
+
it "notifies subscribers with the instance" do
|
|
20
|
+
Number.create(order: RankedModel::MAX_RANK_VALUE)
|
|
21
|
+
second_number = Number.create(order: RankedModel::MAX_RANK_VALUE)
|
|
22
|
+
|
|
23
|
+
expect(@notifications_count).to eq(1)
|
|
24
|
+
expect(@notification_payloads.last[:instance]).to eq(second_number)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "with scope" do
|
|
28
|
+
it "notifies subscribers with the instance and scope" do
|
|
29
|
+
Duck.create(size: RankedModel::MAX_RANK_VALUE, pond: "Shin")
|
|
30
|
+
second_duck = Duck.create(size: RankedModel::MAX_RANK_VALUE, pond: "Shin")
|
|
31
|
+
|
|
32
|
+
expect(@notifications_count).to eq(1)
|
|
33
|
+
expect(@notification_payloads.last[:instance]).to eq(second_duck)
|
|
34
|
+
expect(@notification_payloads.last[:scope]).to eq(:in_shin_pond)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context "with with_same" do
|
|
39
|
+
it "notifies subscribers with the instance and with_same" do
|
|
40
|
+
Duck.create(age: RankedModel::MAX_RANK_VALUE, pond: "Shin")
|
|
41
|
+
second_duck = Duck.create(age: RankedModel::MAX_RANK_VALUE, pond: "Shin")
|
|
42
|
+
|
|
43
|
+
expect(@notifications_count).to eq(1)
|
|
44
|
+
expect(@notification_payloads.last[:instance]).to eq(second_duck)
|
|
45
|
+
expect(@notification_payloads.last[:with_same]).to eq(:pond)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "when rebalancing" do
|
|
51
|
+
it "notifies subscribers with the instance" do
|
|
52
|
+
31.times { Number.create }
|
|
53
|
+
thirty_second_number = Number.create
|
|
54
|
+
|
|
55
|
+
expect(@notifications_count).to eq(1)
|
|
56
|
+
expect(@notification_payloads.last[:instance]).to eq(thirty_second_number)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "with scope" do
|
|
60
|
+
it "notifies subscribers with the instance and scope" do
|
|
61
|
+
31.times { Duck.create(pond: "Shin") }
|
|
62
|
+
thirty_second_duck = Duck.create(pond: "Shin")
|
|
63
|
+
|
|
64
|
+
expect(@notifications_count).to eq(4) # Duck has four ranks
|
|
65
|
+
expect(@notification_payloads.last[:instance]).to eq(thirty_second_duck)
|
|
66
|
+
expect(@notification_payloads.map { |p| p[:scope] }).to include(:in_shin_pond)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context "with with_same" do
|
|
71
|
+
it "notifies subscribers with the instance and with_same" do
|
|
72
|
+
31.times { Duck.create(pond: "Shin") }
|
|
73
|
+
thirty_second_duck = Duck.create(pond: "Shin")
|
|
74
|
+
|
|
75
|
+
expect(@notifications_count).to eq(4)
|
|
76
|
+
expect(@notification_payloads.last[:instance]).to eq(thirty_second_duck)
|
|
77
|
+
expect(@notification_payloads.map { |p| p[:with_same] }).to include(:pond)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context "when not rearranging or rebalancing" do
|
|
83
|
+
it "does not notify subscribers" do
|
|
84
|
+
Number.create
|
|
85
|
+
|
|
86
|
+
expect(@notifications_count).to eq(0)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ranked-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Beale
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-08-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -137,6 +137,7 @@ files:
|
|
|
137
137
|
- ".rspec"
|
|
138
138
|
- ".travis.yml"
|
|
139
139
|
- Appraisals
|
|
140
|
+
- CHANGELOG.md
|
|
140
141
|
- Gemfile
|
|
141
142
|
- LICENSE
|
|
142
143
|
- Rakefile
|
|
@@ -157,6 +158,7 @@ files:
|
|
|
157
158
|
- spec/duck-model/lots_of_ducks_spec.rb
|
|
158
159
|
- spec/duck-model/wrong_ducks_spec.rb
|
|
159
160
|
- spec/ego-model/ego_spec.rb
|
|
161
|
+
- spec/notifications_spec.rb
|
|
160
162
|
- spec/number-model/number_spec.rb
|
|
161
163
|
- spec/player-model/records_already_exist_spec.rb
|
|
162
164
|
- spec/ranked-model/ranker_spec.rb
|
|
@@ -197,6 +199,7 @@ test_files:
|
|
|
197
199
|
- spec/duck-model/lots_of_ducks_spec.rb
|
|
198
200
|
- spec/duck-model/wrong_ducks_spec.rb
|
|
199
201
|
- spec/ego-model/ego_spec.rb
|
|
202
|
+
- spec/notifications_spec.rb
|
|
200
203
|
- spec/number-model/number_spec.rb
|
|
201
204
|
- spec/player-model/records_already_exist_spec.rb
|
|
202
205
|
- spec/ranked-model/ranker_spec.rb
|