ranked-model 0.4.8 → 0.4.11

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.
@@ -1,356 +1,367 @@
1
- module RankedModel
2
-
3
- class InvalidScope < StandardError; end
4
- class InvalidField < StandardError; end
5
-
6
- class Ranker
7
- attr_accessor :name, :column, :scope, :with_same, :class_name, :unless
8
-
9
- def initialize name, options={}
10
- self.name = name.to_sym
11
- self.column = options[:column] || name
12
- self.class_name = options[:class_name]
13
-
14
- [ :scope, :with_same, :unless ].each do |key|
15
- self.send "#{key}=", options[key]
16
- end
17
- end
18
-
19
- def with instance
20
- Mapper.new self, instance
21
- end
22
-
23
- class Mapper
24
- attr_accessor :ranker, :instance
25
-
26
- def initialize ranker, instance
27
- self.ranker = ranker
28
- self.instance = instance
29
-
30
- validate_ranker_for_instance!
31
- end
32
-
33
- def validate_ranker_for_instance!
34
- if ranker.scope && !instance_class.respond_to?(ranker.scope)
35
- raise RankedModel::InvalidScope, %Q{No scope called "#{ranker.scope}" found in model}
36
- end
37
-
38
- if ranker.with_same
39
- if (case ranker.with_same
40
- when Symbol
41
- !instance.respond_to?(ranker.with_same)
42
- when Array
43
- array_element = ranker.with_same.detect {|attr| !instance.respond_to?(attr) }
44
- else
45
- false
46
- end)
47
- raise RankedModel::InvalidField, %Q{No field called "#{array_element || ranker.with_same}" found in model}
48
- end
49
- end
50
- end
51
-
52
- def handle_ranking
53
- case ranker.unless
54
- when Proc
55
- return if ranker.unless.call(instance)
56
- when Symbol
57
- return if instance.send(ranker.unless)
58
- end
59
-
60
- update_index_from_position
61
- assure_unique_position
62
- end
63
-
64
- def update_rank! value
65
- # Bypass callbacks
66
- #
67
- instance_class.
68
- where(instance_class.primary_key => instance.id).
69
- update_all(ranker.column => value)
70
- end
71
-
72
- def reset_ranks!
73
- finder.update_all(ranker.column => nil)
74
- end
75
-
76
- def position
77
- instance.send "#{ranker.name}_position"
78
- end
79
-
80
- def relative_rank
81
- escaped_column = instance_class.connection.quote_column_name ranker.column
82
-
83
- finder.where("#{escaped_column} < #{rank}").count(:all)
84
- end
85
-
86
- def rank
87
- instance.send "#{ranker.column}"
88
- end
89
-
90
- def current_at_position _pos
91
- if (ordered_instance = finder.offset(_pos).first)
92
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
93
- end
94
- end
95
-
96
- def has_rank?
97
- !rank.nil?
98
- end
99
-
100
- private
101
-
102
- def reset_cache
103
- @finder, @current_order, @current_first, @current_last = nil
104
- end
105
-
106
- def instance_class
107
- ranker.class_name.nil? ? instance.class : ranker.class_name.constantize
108
- end
109
-
110
- def position_at value
111
- instance.send "#{ranker.name}_position=", value
112
- update_index_from_position
113
- end
114
-
115
- def rank_at value
116
- instance.send "#{ranker.column}=", value
117
- end
118
-
119
- def rank_changed?
120
- instance.send "#{ranker.column}_changed?"
121
- end
122
-
123
- def new_record?
124
- instance.new_record?
125
- end
126
-
127
- def update_index_from_position
128
- case position
129
- when :first, 'first'
130
- if current_first && current_first.rank
131
- rank_at_average current_first.rank, RankedModel::MIN_RANK_VALUE
132
- else
133
- position_at :middle
134
- end
135
- when :last, 'last'
136
- if current_last && current_last.rank
137
- rank_at_average current_last.rank, RankedModel::MAX_RANK_VALUE
138
- else
139
- position_at :middle
140
- end
141
- when :middle, 'middle'
142
- rank_at_average RankedModel::MIN_RANK_VALUE, RankedModel::MAX_RANK_VALUE
143
- when :down, 'down'
144
- neighbors = find_next_two(rank)
145
- if neighbors[:lower]
146
- min = neighbors[:lower].rank
147
- max = neighbors[:upper] ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE
148
- rank_at_average min, max
149
- end
150
- when :up, 'up'
151
- neighbors = find_previous_two(rank)
152
- if neighbors[:upper]
153
- max = neighbors[:upper].rank
154
- min = neighbors[:lower] ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE
155
- rank_at_average min, max
156
- end
157
- when String
158
- position_at position.to_i
159
- when 0
160
- position_at :first
161
- when Integer
162
- neighbors = neighbors_at_position(position)
163
- min = ((neighbors[:lower] && neighbors[:lower].has_rank?) ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE)
164
- max = ((neighbors[:upper] && neighbors[:upper].has_rank?) ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE)
165
- rank_at_average min, max
166
- when NilClass
167
- if !rank
168
- position_at :last
169
- end
170
- end
171
- end
172
-
173
- def rank_at_average(min, max)
174
- if (max - min).between?(-1, 1) # No room at the inn...
175
- rebalance_ranks
176
- position_at position
177
- else
178
- rank_at( ( ( max - min ).to_f / 2 ).ceil + min )
179
- end
180
- end
181
-
182
- def assure_unique_position
183
- if ( new_record? || rank_changed? )
184
- if (rank > RankedModel::MAX_RANK_VALUE) || rank_taken?
185
- rearrange_ranks
186
- end
187
- end
188
- end
189
-
190
- def rearrange_ranks
191
- _scope = finder
192
- escaped_column = instance_class.connection.quote_column_name ranker.column
193
- # If there is room at the bottom of the list and we're added to the very top of the list...
194
- if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
195
- # ...then move everyone else down 1 to make room for us at the end
196
- _scope.
197
- where( instance_class.arel_table[ranker.column].lteq(rank) ).
198
- update_all( "#{escaped_column} = #{escaped_column} - 1" )
199
- # If there is room at the top of the list and we're added below the last value in the list...
200
- elsif current_last.rank && current_last.rank < (RankedModel::MAX_RANK_VALUE - 1) && rank < current_last.rank
201
- # ...then move everyone else at or above our desired rank up 1 to make room for us
202
- _scope.
203
- where( instance_class.arel_table[ranker.column].gteq(rank) ).
204
- update_all( "#{escaped_column} = #{escaped_column} + 1" )
205
- # If there is room at the bottom of the list and we're added above the lowest value in the list...
206
- elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank
207
- # ...then move everyone else below us down 1 and change our rank down 1 to avoid the collission
208
- _scope.
209
- where( instance_class.arel_table[ranker.column].lt(rank) ).
210
- update_all( "#{escaped_column} = #{escaped_column} - 1" )
211
- rank_at( rank - 1 )
212
- else
213
- rebalance_ranks
214
- end
215
- end
216
-
217
- def rebalance_ranks
218
- ActiveRecord::Base.transaction do
219
- if rank && instance.persisted?
220
- origin = current_order.index { |item| item.instance.id == instance.id }
221
- if origin
222
- destination = current_order.index { |item| rank <= item.rank }
223
- destination -= 1 if origin < destination
224
-
225
- current_order.insert destination, current_order.delete_at(origin)
226
- end
227
- end
228
-
229
- gaps = current_order.size + 1
230
- range = (RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE).to_f
231
- gap_size = (range / gaps).ceil
232
-
233
- reset_ranks!
234
-
235
- current_order.each.with_index(1) do |item, position|
236
- new_rank = (gap_size * position) + RankedModel::MIN_RANK_VALUE
237
-
238
- if item.instance.id == instance.id
239
- rank_at new_rank
240
- else
241
- item.update_rank! new_rank
242
- end
243
- end
244
-
245
- reset_cache
246
- end
247
- end
248
-
249
- def finder(order = :asc)
250
- @finder ||= {}
251
- @finder[order] ||= begin
252
- _finder = instance_class
253
- columns = [instance_class.primary_key.to_sym, ranker.column]
254
-
255
- if ranker.scope
256
- _finder = _finder.send ranker.scope
257
- end
258
-
259
- case ranker.with_same
260
- when Symbol
261
- columns << ranker.with_same
262
- _finder = _finder.where \
263
- ranker.with_same => instance.attributes[ranker.with_same.to_s]
264
- when Array
265
- ranker.with_same.each do |column|
266
- columns << column
267
- _finder = _finder.where column => instance.attributes[column.to_s]
268
- end
269
- end
270
-
271
- unless new_record?
272
- _finder = _finder.where.not instance_class.primary_key.to_sym => instance.id
273
- end
274
-
275
- _finder.reorder(ranker.column.to_sym => order).select(columns)
276
- end
277
- end
278
-
279
- def current_order
280
- @current_order ||= begin
281
- finder.unscope(where: instance_class.primary_key.to_sym).collect { |ordered_instance|
282
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
283
- }
284
- end
285
- end
286
-
287
- def current_first
288
- @current_first ||= begin
289
- if (ordered_instance = finder.first)
290
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
291
- end
292
- end
293
- end
294
-
295
- def current_last
296
- @current_last ||= begin
297
- if (ordered_instance = finder.last)
298
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
299
- end
300
- end
301
- end
302
-
303
- def rank_taken?
304
- finder.except(:order).where(ranker.column => rank).exists?
305
- end
306
-
307
- def neighbors_at_position _pos
308
- if _pos > 0
309
- if (ordered_instances = finder.offset(_pos-1).limit(2).to_a)
310
- if ordered_instances[1]
311
- { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
312
- :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
313
- elsif ordered_instances[0]
314
- { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
315
- else
316
- { :lower => current_last }
317
- end
318
- end
319
- else
320
- if (ordered_instance = finder.first)
321
- { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instance ) }
322
- else
323
- {}
324
- end
325
- end
326
- end
327
-
328
- def find_next_two _rank
329
- ordered_instances = finder.where(instance_class.arel_table[ranker.column].gt _rank).limit(2)
330
- if ordered_instances[1]
331
- { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
332
- :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
333
- elsif ordered_instances[0]
334
- { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
335
- else
336
- {}
337
- end
338
- end
339
-
340
- def find_previous_two _rank
341
- ordered_instances = finder(:desc).where(instance_class.arel_table[ranker.column].lt _rank).limit(2)
342
- if ordered_instances[1]
343
- { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
344
- :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
345
- elsif ordered_instances[0]
346
- { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
347
- else
348
- {}
349
- end
350
- end
351
-
352
- end
353
-
354
- end
355
-
356
- end
1
+ module RankedModel
2
+
3
+ class InvalidScope < StandardError; end
4
+ class InvalidField < StandardError; end
5
+
6
+ class Ranker
7
+ attr_accessor :name, :column, :scope, :with_same, :class_name, :unless
8
+
9
+ def initialize name, options={}
10
+ self.name = name.to_sym
11
+ self.column = options[:column] || name
12
+ self.class_name = options[:class_name]
13
+
14
+ [ :scope, :with_same, :unless ].each do |key|
15
+ self.send "#{key}=", options[key]
16
+ end
17
+ end
18
+
19
+ def with instance
20
+ Mapper.new self, instance
21
+ end
22
+
23
+ class Mapper
24
+ attr_accessor :ranker, :instance
25
+
26
+ def initialize ranker, instance
27
+ self.ranker = ranker
28
+ self.instance = instance
29
+
30
+ validate_ranker_for_instance!
31
+ end
32
+
33
+ def validate_ranker_for_instance!
34
+ if ranker.scope && !instance_class.respond_to?(ranker.scope)
35
+ raise RankedModel::InvalidScope, %Q{No scope called "#{ranker.scope}" found in model}
36
+ end
37
+
38
+ if ranker.with_same
39
+ if (case ranker.with_same
40
+ when Symbol
41
+ !instance.respond_to?(ranker.with_same)
42
+ when Array
43
+ array_element = ranker.with_same.detect {|attr| !instance.respond_to?(attr) }
44
+ else
45
+ false
46
+ end)
47
+ raise RankedModel::InvalidField, %Q{No field called "#{array_element || ranker.with_same}" found in model}
48
+ end
49
+ end
50
+ end
51
+
52
+ def handle_ranking
53
+ case ranker.unless
54
+ when Proc
55
+ return if ranker.unless.call(instance)
56
+ when Symbol
57
+ return if instance.send(ranker.unless)
58
+ end
59
+
60
+ update_index_from_position
61
+ assure_unique_position
62
+ end
63
+
64
+ def update_rank! value
65
+ # Bypass callbacks
66
+ #
67
+ instance_class.
68
+ where(instance_class.primary_key => instance.id).
69
+ update_all(ranker.column => value)
70
+ end
71
+
72
+ def reset_ranks!
73
+ finder.update_all(ranker.column => nil)
74
+ end
75
+
76
+ def position
77
+ instance.send "#{ranker.name}_position"
78
+ end
79
+
80
+ def relative_rank
81
+ escaped_column = instance_class.connection.quote_column_name ranker.column
82
+
83
+ finder.where("#{escaped_column} < #{rank}").count(:all)
84
+ end
85
+
86
+ def rank
87
+ instance.send "#{ranker.column}"
88
+ end
89
+
90
+ def current_at_position _pos
91
+ if (ordered_instance = finder.offset(_pos).first)
92
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
93
+ end
94
+ end
95
+
96
+ def has_rank?
97
+ !rank.nil?
98
+ end
99
+
100
+ private
101
+
102
+ def reset_cache
103
+ @finder, @current_order, @current_first, @current_last = nil
104
+ end
105
+
106
+ def instance_class
107
+ ranker.class_name.nil? ? instance.class : ranker.class_name.constantize
108
+ end
109
+
110
+ def position_at value
111
+ instance.send "#{ranker.name}_position=", value
112
+ update_index_from_position
113
+ end
114
+
115
+ def rank_at value
116
+ instance.send "#{ranker.column}=", value
117
+ instance.send "#{ranker.name}_position=", relative_rank unless position.is_a?(Integer)
118
+ end
119
+
120
+ def rank_changed?
121
+ instance.send "#{ranker.column}_changed?"
122
+ end
123
+
124
+ def new_record?
125
+ instance.new_record?
126
+ end
127
+
128
+ def update_index_from_position
129
+ case position
130
+ when :first, 'first'
131
+ if current_first && current_first.rank
132
+ rank_at_average current_first.rank, RankedModel::MIN_RANK_VALUE
133
+ else
134
+ position_at :middle
135
+ end
136
+ when :last, 'last'
137
+ if current_last && current_last.rank
138
+ rank_at_average current_last.rank, RankedModel::MAX_RANK_VALUE
139
+ else
140
+ position_at :middle
141
+ end
142
+ when :middle, 'middle'
143
+ rank_at_average RankedModel::MIN_RANK_VALUE, RankedModel::MAX_RANK_VALUE
144
+ when :down, 'down'
145
+ neighbors = find_next_two(rank)
146
+ if neighbors[:lower]
147
+ min = neighbors[:lower].rank
148
+ max = neighbors[:upper] ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE
149
+ rank_at_average min, max
150
+ end
151
+ when :up, 'up'
152
+ neighbors = find_previous_two(rank)
153
+ if neighbors[:upper]
154
+ max = neighbors[:upper].rank
155
+ min = neighbors[:lower] ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE
156
+ rank_at_average min, max
157
+ end
158
+ when String
159
+ position_at position.to_i
160
+ when 0
161
+ position_at :first
162
+ when Integer
163
+ neighbors = neighbors_at_position(position)
164
+ min = ((neighbors[:lower] && neighbors[:lower].has_rank?) ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE)
165
+ max = ((neighbors[:upper] && neighbors[:upper].has_rank?) ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE)
166
+ rank_at_average min, max
167
+ when NilClass
168
+ if !rank
169
+ position_at :last
170
+ end
171
+ end
172
+ end
173
+
174
+ def rank_at_average(min, max)
175
+ if (max - min).between?(-1, 1) # No room at the inn...
176
+ notify_ranks_updated { rebalance_ranks }
177
+ position_at position
178
+ else
179
+ rank_at( ( ( max - min ).to_f / 2 ).ceil + min )
180
+ end
181
+ end
182
+
183
+ def assure_unique_position
184
+ if ( new_record? || rank_changed? )
185
+ if (rank > RankedModel::MAX_RANK_VALUE) || rank_taken?
186
+ notify_ranks_updated { rearrange_ranks }
187
+ end
188
+ end
189
+ end
190
+
191
+ def rearrange_ranks
192
+ _scope = finder
193
+ escaped_column = instance_class.connection.quote_column_name ranker.column
194
+ # If there is room at the bottom of the list and we're added to the very top of the list...
195
+ if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
196
+ # ...then move everyone else down 1 to make room for us at the end
197
+ _scope.
198
+ where( instance_class.arel_table[ranker.column].lteq(rank) ).
199
+ update_all( "#{escaped_column} = #{escaped_column} - 1" )
200
+ # If there is room at the top of the list and we're added below the last value in the list...
201
+ elsif current_last.rank && current_last.rank < (RankedModel::MAX_RANK_VALUE - 1) && rank < current_last.rank
202
+ # ...then move everyone else at or above our desired rank up 1 to make room for us
203
+ _scope.
204
+ where( instance_class.arel_table[ranker.column].gteq(rank) ).
205
+ update_all( "#{escaped_column} = #{escaped_column} + 1" )
206
+ # If there is room at the bottom of the list and we're added above the lowest value in the list...
207
+ elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank
208
+ # ...then move everyone else below us down 1 and change our rank down 1 to avoid the collission
209
+ _scope.
210
+ where( instance_class.arel_table[ranker.column].lt(rank) ).
211
+ update_all( "#{escaped_column} = #{escaped_column} - 1" )
212
+ rank_at( rank - 1 )
213
+ else
214
+ rebalance_ranks
215
+ end
216
+ end
217
+
218
+ def rebalance_ranks
219
+ ActiveRecord::Base.transaction do
220
+ if rank && instance.persisted?
221
+ origin = current_order.index { |item| item.instance.id == instance.id }
222
+ if origin
223
+ destination = current_order.index { |item| rank <= item.rank }
224
+ destination -= 1 if origin < destination
225
+
226
+ current_order.insert destination, current_order.delete_at(origin)
227
+ end
228
+ end
229
+
230
+ gaps = current_order.size + 1
231
+ range = (RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE).to_f
232
+ gap_size = (range / gaps).ceil
233
+
234
+ reset_ranks!
235
+
236
+ current_order.each.with_index(1) do |item, position|
237
+ new_rank = (gap_size * position) + RankedModel::MIN_RANK_VALUE
238
+
239
+ if item.instance.id == instance.id
240
+ instance.send "#{ranker.column}=", new_rank
241
+ else
242
+ item.update_rank! new_rank
243
+ end
244
+ end
245
+
246
+ reset_cache
247
+ end
248
+ end
249
+
250
+ def finder(order = :asc)
251
+ @finder ||= {}
252
+ @finder[order] ||= begin
253
+ _finder = instance_class
254
+ columns = [instance_class.primary_key.to_sym, ranker.column]
255
+
256
+ if ranker.scope
257
+ _finder = _finder.send ranker.scope
258
+ end
259
+
260
+ case ranker.with_same
261
+ when Symbol
262
+ columns << ranker.with_same
263
+ _finder = _finder.where \
264
+ ranker.with_same => instance.attributes[ranker.with_same.to_s]
265
+ when Array
266
+ ranker.with_same.each do |column|
267
+ columns << column
268
+ _finder = _finder.where column => instance.attributes[column.to_s]
269
+ end
270
+ end
271
+
272
+ unless new_record?
273
+ _finder = _finder.where.not instance_class.primary_key.to_sym => instance.id
274
+ end
275
+
276
+ _finder.reorder(ranker.column.to_sym => order).select(columns)
277
+ end
278
+ end
279
+
280
+ def current_order
281
+ @current_order ||= begin
282
+ finder.unscope(where: instance_class.primary_key.to_sym).collect { |ordered_instance|
283
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
284
+ }
285
+ end
286
+ end
287
+
288
+ def current_first
289
+ @current_first ||= begin
290
+ if (ordered_instance = finder.first)
291
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
292
+ end
293
+ end
294
+ end
295
+
296
+ def current_last
297
+ @current_last ||= begin
298
+ if (ordered_instance = finder.last)
299
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
300
+ end
301
+ end
302
+ end
303
+
304
+ def rank_taken?
305
+ finder.except(:order).where(ranker.column => rank).exists?
306
+ end
307
+
308
+ def neighbors_at_position _pos
309
+ if _pos > 0
310
+ if (ordered_instances = finder.offset(_pos-1).limit(2).to_a)
311
+ if ordered_instances[1]
312
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
313
+ :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
314
+ elsif ordered_instances[0]
315
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
316
+ else
317
+ { :lower => current_last }
318
+ end
319
+ end
320
+ else
321
+ if (ordered_instance = finder.first)
322
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instance ) }
323
+ else
324
+ {}
325
+ end
326
+ end
327
+ end
328
+
329
+ def find_next_two _rank
330
+ ordered_instances = finder.where(instance_class.arel_table[ranker.column].gt _rank).limit(2)
331
+ if ordered_instances[1]
332
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
333
+ :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
334
+ elsif ordered_instances[0]
335
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
336
+ else
337
+ {}
338
+ end
339
+ end
340
+
341
+ def find_previous_two _rank
342
+ ordered_instances = finder(:desc).where(instance_class.arel_table[ranker.column].lt _rank).limit(2)
343
+ if ordered_instances[1]
344
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
345
+ :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
346
+ elsif ordered_instances[0]
347
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
348
+ else
349
+ {}
350
+ end
351
+ end
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
363
+ end
364
+
365
+ end
366
+
367
+ end