ranked-model 0.4.9 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61451e859060c7d8a3f889bd54327b103797d728312cedef66efa1b25d11767f
4
- data.tar.gz: 2e1df0f514decf2e43667c2d0139e510202fc0ebc32aed758831ea20497aaf1a
3
+ metadata.gz: 329351cc90908d43dfeda2987183ebedff1abe8cbefc05afe1d0bd6760df2c41
4
+ data.tar.gz: f99a8987935928d33ac77555ff9bd5614002db38fffc7cd1a6c85449e236a42b
5
5
  SHA512:
6
- metadata.gz: d046ed8e55533b8f453a67e0c705738e9353b22b20b01310381a56195803ee066cd647e1b8801484ca79518a8ea2394577841d4b9c4eb97d12f0b36246c1bfc7
7
- data.tar.gz: ca05b38d74cbe11544cabb70890c64d26c9a94c4608d2b4d7da1253535efc03775ef3fed80fc9b93709f503f3f53a8cd725b7f2b8c43ad8565a72139f0ec53e5
6
+ metadata.gz: 6b31ddb712c2425d6a59c918ab506f6e2855f9a40575cc298df8d1271f701741a5bfa88040410299226c46ace4cec00459a1f5d804b303a0326de428c1035cd1
7
+ data.tar.gz: 02ef12162bec60ee6b82f4f8dd0200ed82926bed9d4a6e5e4e77ce6d8c46ec7d2343d347ecf6e6a234cadec2e3e42042f7097bf9e7791fe2ab75342ac89c2777
@@ -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.1
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@v3
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
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.4.10] - 2024-08-09
4
+
5
+ - Publish ActiveSupport notifications on rebalancing. Thanks @tjvc!
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
- [![Build Status](https://travis-ci.org/mixonic/ranked-model.png)](https://travis-ci.org/mixonic/ranked-model)
3
+ [![Build Status](https://github.com/brendon/ranked-model/actions/workflows/ci.yml/badge.svg)](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
 
@@ -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
@@ -1,3 +1,3 @@
1
1
  module RankedModel
2
- VERSION = "0.4.9"
2
+ VERSION = "0.4.10"
3
3
  end
@@ -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.9
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: 2023-08-22 00:00:00.000000000 Z
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