ranked-model 0.4.2 → 0.4.7

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.
@@ -5,15 +5,18 @@ source "https://rubygems.org"
5
5
  gem "activerecord", "~> 5.2.0"
6
6
 
7
7
  group :sqlite do
8
- gem "sqlite3", platform: :ruby
8
+ gem "sqlite3", "~> 1.3.13", platform: :ruby
9
+ gem "activerecord-jdbcsqlite3-adapter", "~> 52.0", platform: :jruby
9
10
  end
10
11
 
11
- group :mysql do
12
- gem "mysql2", "~> 0.4.10", platform: :ruby
12
+ group :postgresql do
13
+ gem "pg", "~> 1.2.0", platform: :ruby
14
+ gem "activerecord-jdbcpostgresql-adapter", "~> 52.0", platform: :jruby
13
15
  end
14
16
 
15
- group :postgresql do
16
- gem "pg", "~> 0.21.0", platform: :ruby
17
+ group :mysql do
18
+ gem "mysql2", "~> 0.5.0", platform: :ruby
19
+ gem "activerecord-jdbcmysql-adapter", "~> 52.0", platform: :jruby
17
20
  end
18
21
 
19
22
  gemspec path: "../"
@@ -0,0 +1,22 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 6.0.0"
6
+
7
+ group :sqlite do
8
+ gem "sqlite3", "~> 1.4", platform: :ruby
9
+ gem "activerecord-jdbcsqlite3-adapter", "~> 60.0", platform: :jruby
10
+ end
11
+
12
+ group :postgresql do
13
+ gem "pg", "~> 1.2.0", platform: :ruby
14
+ gem "activerecord-jdbcpostgresql-adapter", "~> 60.0", platform: :jruby
15
+ end
16
+
17
+ group :mysql do
18
+ gem "mysql2", "~> 0.5.0", platform: :ruby
19
+ gem "activerecord-jdbcmysql-adapter", "~> 60.0", platform: :jruby
20
+ end
21
+
22
+ gemspec path: "../"
@@ -3,6 +3,8 @@ require File.dirname(__FILE__)+'/ranked-model/railtie' if defined?(Rails::Railti
3
3
 
4
4
  module RankedModel
5
5
 
6
+ class NonNilColumnDefault < StandardError; end
7
+
6
8
  # Signed INT in MySQL
7
9
  #
8
10
  MAX_RANK_VALUE = 2147483647
@@ -18,7 +20,7 @@ module RankedModel
18
20
  before_save :handle_ranking
19
21
 
20
22
  scope :rank, lambda { |name|
21
- order ranker(name.to_sym).column
23
+ reorder ranker(name.to_sym).column
22
24
  }
23
25
  end
24
26
 
@@ -45,6 +47,11 @@ module RankedModel
45
47
  def ranks *args
46
48
  self.rankers ||= []
47
49
  ranker = RankedModel::Ranker.new(*args)
50
+
51
+ if column_default(ranker)
52
+ raise NonNilColumnDefault, %Q{Your ranked model column "#{ranker.name}" must not have a default value in the database.}
53
+ end
54
+
48
55
  self.rankers << ranker
49
56
  attr_reader "#{ranker.name}_position"
50
57
  define_method "#{ranker.name}_position=" do |position|
@@ -54,9 +61,17 @@ module RankedModel
54
61
  end
55
62
  end
56
63
 
64
+ define_method "#{ranker.name}_rank" do
65
+ ranker.with(self).relative_rank
66
+ end
67
+
57
68
  public "#{ranker.name}_position", "#{ranker.name}_position="
58
69
  end
59
70
 
71
+ def column_default ranker
72
+ column_defaults[ranker.name.to_s] if ActiveRecord::Base.connected? && table_exists?
73
+ end
74
+
60
75
  end
61
76
 
62
77
  end
@@ -69,10 +69,20 @@ module RankedModel
69
69
  update_all(ranker.column => value)
70
70
  end
71
71
 
72
+ def reset_ranks!
73
+ finder.update_all(ranker.column => nil)
74
+ end
75
+
72
76
  def position
73
77
  instance.send "#{ranker.name}_position"
74
78
  end
75
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
+
76
86
  def rank
77
87
  instance.send "#{ranker.column}"
78
88
  end
@@ -171,7 +181,7 @@ module RankedModel
171
181
 
172
182
  def assure_unique_position
173
183
  if ( new_record? || rank_changed? )
174
- if (rank > RankedModel::MAX_RANK_VALUE) || current_at_rank(rank)
184
+ if (rank > RankedModel::MAX_RANK_VALUE) || rank_taken?
175
185
  rearrange_ranks
176
186
  end
177
187
  end
@@ -179,7 +189,7 @@ module RankedModel
179
189
 
180
190
  def rearrange_ranks
181
191
  _scope = finder
182
- escaped_column = ActiveRecord::Base.connection.quote_column_name ranker.column
192
+ escaped_column = instance_class.connection.quote_column_name ranker.column
183
193
  # If there is room at the bottom of the list and we're added to the very top of the list...
184
194
  if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
185
195
  # ...then move everyone else down 1 to make room for us at the end
@@ -205,29 +215,35 @@ module RankedModel
205
215
  end
206
216
 
207
217
  def rebalance_ranks
208
- if rank
209
- origin = current_order.index { |item| item.instance.id == instance.id }
210
- destination = current_order.index { |item| rank <= item.rank }
211
- destination -= 1 if origin < destination
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
212
228
 
213
- current_order.insert destination, current_order.delete_at(origin)
214
- end
229
+ gaps = current_order.size + 1
230
+ range = (RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE).to_f
231
+ gap_size = (range / gaps).ceil
215
232
 
216
- gaps = current_order.size + 1
217
- range = (RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE).to_f
218
- gap_size = (range / gaps).ceil
233
+ reset_ranks!
219
234
 
220
- current_order.each.with_index(1) do |item, position|
221
- new_rank = (gap_size * position) + RankedModel::MIN_RANK_VALUE
235
+ current_order.each.with_index(1) do |item, position|
236
+ new_rank = (gap_size * position) + RankedModel::MIN_RANK_VALUE
222
237
 
223
- if item.instance.id == instance.id
224
- rank_at new_rank
225
- else
226
- item.update_rank! new_rank
238
+ if item.instance.id == instance.id
239
+ rank_at new_rank
240
+ else
241
+ item.update_rank! new_rank
242
+ end
227
243
  end
228
- end
229
244
 
230
- reset_cache
245
+ reset_cache
246
+ end
231
247
  end
232
248
 
233
249
  def finder(order = :asc)
@@ -256,7 +272,7 @@ module RankedModel
256
272
  _finder = _finder.where.not instance_class.primary_key.to_sym => instance.id
257
273
  end
258
274
 
259
- _finder.order(ranker.column.to_sym => order).select(columns)
275
+ _finder.reorder(ranker.column.to_sym => order).select(columns)
260
276
  end
261
277
  end
262
278
 
@@ -286,13 +302,8 @@ module RankedModel
286
302
  end
287
303
  end
288
304
 
289
- def current_at_rank _rank
290
- if (ordered_instance = finder.
291
- except( :order ).
292
- where( ranker.column => _rank ).
293
- first)
294
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
295
- end
305
+ def rank_taken?
306
+ finder.except(:order).where(ranker.column => rank).exists?
296
307
  end
297
308
 
298
309
  def neighbors_at_position _pos
@@ -1,3 +1,3 @@
1
1
  module RankedModel
2
- VERSION = "0.4.2"
2
+ VERSION = "0.4.7"
3
3
  end
@@ -9,11 +9,11 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Matthew Beale"]
10
10
  s.email = ["matt.beale@madhatted.com"]
11
11
  s.homepage = "https://github.com/mixonic/ranked-model"
12
- s.summary = %q{An acts_as_sortable replacement built for Rails 3 & 4}
13
- s.description = %q{ranked-model is a modern row sorting library built for Rails 3 & 4. It uses ARel aggressively and is better optimized than most other libraries.}
12
+ s.summary = %q{An acts_as_sortable replacement built for Rails 4.2+}
13
+ s.description = %q{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.}
14
14
  s.license = 'MIT'
15
15
 
16
- s.add_dependency "activerecord", ">= 4.1.16"
16
+ s.add_dependency "activerecord", ">= 4.2"
17
17
  s.add_development_dependency "rspec", "~> 3"
18
18
  s.add_development_dependency "rspec-its"
19
19
  s.add_development_dependency "mocha"
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ColumnDefaultDuck' do
4
+
5
+ it "should raise an error if we try to initialise ranked_model on a column with a default value" do
6
+ expect {
7
+ class ColumnDefaultDuck < ActiveRecord::Base
8
+ include RankedModel
9
+ ranks :size, :with_same => :pond
10
+ end
11
+ }.to raise_error(RankedModel::NonNilColumnDefault, 'Your ranked model column "size" must not have a default value in the database.')
12
+ end
13
+
14
+ it "should not raise an error if we don't have a database connection when checking for default value" do
15
+ begin
16
+ ActiveRecord::Base.remove_connection
17
+
18
+ expect {
19
+ class ColumnDefaultDuck < ActiveRecord::Base
20
+ include RankedModel
21
+ ranks :size, :with_same => :pond
22
+ end
23
+ }.not_to raise_error
24
+ ensure
25
+ ActiveRecord::Base.establish_connection(ENV['DB'].to_sym)
26
+ end
27
+ end
28
+
29
+ end
@@ -10,12 +10,16 @@ describe Duck do
10
10
 
11
11
  it { expect(subject).to respond_to(:row_position) }
12
12
  it { expect(subject).to respond_to(:row_position=) }
13
+ it { expect(subject).to respond_to(:row_rank) }
13
14
  it { expect(subject).to respond_to(:size_position) }
14
15
  it { expect(subject).to respond_to(:size_position=) }
16
+ it { expect(subject).to respond_to(:size_rank) }
15
17
  it { expect(subject).to respond_to(:age_position) }
16
18
  it { expect(subject).to respond_to(:age_position=) }
19
+ it { expect(subject).to respond_to(:age_rank) }
17
20
  it { expect(subject).to respond_to(:landing_order_position) }
18
21
  it { expect(subject).to respond_to(:landing_order_position=) }
22
+ it { expect(subject).to respond_to(:landing_order_rank) }
19
23
 
20
24
  end
21
25
 
@@ -44,9 +48,9 @@ describe Duck do
44
48
  }
45
49
  @ducks.each { |name, duck|
46
50
  duck.reload
47
- duck.update_attribute :row_position, 0
48
- duck.update_attribute :size_position, 0
49
- duck.update_attribute :age_position, 0
51
+ duck.update :row_position => 0
52
+ duck.update :size_position => 0
53
+ duck.update :age_position => 0
50
54
  duck.save!
51
55
  }
52
56
  @ducks.each {|name, duck| duck.reload }
@@ -55,8 +59,8 @@ describe Duck do
55
59
  describe "sorting by size on in_shin_pond" do
56
60
 
57
61
  before {
58
- @ducks[:quacky].update_attribute :size_position, 0
59
- @ducks[:wingy].update_attribute :size_position, 2
62
+ @ducks[:quacky].update :size_position => 0
63
+ @ducks[:wingy].update :size_position => 2
60
64
  }
61
65
 
62
66
  subject { Duck.in_shin_pond.rank(:size).to_a }
@@ -72,8 +76,8 @@ describe Duck do
72
76
  describe "sorting by age on Shin pond" do
73
77
 
74
78
  before {
75
- @ducks[:feathers].update_attribute :age_position, 0
76
- @ducks[:wingy].update_attribute :age_position, 0
79
+ @ducks[:feathers].update :age_position => 0
80
+ @ducks[:wingy].update :age_position => 0
77
81
  }
78
82
 
79
83
  subject { Duck.where(:pond => 'Shin').rank(:age).to_a }
@@ -89,10 +93,10 @@ describe Duck do
89
93
  describe "sorting by row" do
90
94
 
91
95
  before {
92
- @ducks[:beaky].update_attribute :row_position, 0
93
- @ducks[:webby].update_attribute :row_position, 2
94
- @ducks[:waddly].update_attribute :row_position, 2
95
- @ducks[:wingy].update_attribute :row_position, 6
96
+ @ducks[:beaky].update :row_position => 0
97
+ @ducks[:webby].update :row_position => 2
98
+ @ducks[:waddly].update :row_position => 2
99
+ @ducks[:wingy].update :row_position => 6
96
100
  }
97
101
 
98
102
  subject { Duck.rank(:row).to_a }
@@ -108,13 +112,13 @@ describe Duck do
108
112
  describe "mixed sorting by" do
109
113
 
110
114
  before {
111
- @ducks[:quacky].update_attribute :size_position, 0
112
- @ducks[:beaky].update_attribute :row_position, 0
113
- @ducks[:webby].update_attribute :row_position, 2
114
- @ducks[:wingy].update_attribute :size_position, 1
115
- @ducks[:waddly].update_attribute :row_position, 2
116
- @ducks[:wingy].update_attribute :row_position, 6
117
- @ducks[:webby].update_attribute :row_position, 6
115
+ @ducks[:quacky].update :size_position => 0
116
+ @ducks[:beaky].update :row_position => 0
117
+ @ducks[:webby].update :row_position => 2
118
+ @ducks[:wingy].update :size_position => 1
119
+ @ducks[:waddly].update :row_position => 2
120
+ @ducks[:wingy].update :row_position => 6
121
+ @ducks[:webby].update :row_position => 6
118
122
  }
119
123
 
120
124
  describe "row" do
@@ -149,7 +153,7 @@ describe Duck do
149
153
  # puts Duck.rank(:age).collect {|duck| "#{duck.name} #{duck.age}" }
150
154
  duck = Duck.rank(:age)[2]
151
155
  expect(->{
152
- duck.update_attribute :name, 'New Name'
156
+ duck.update :name => 'New Name'
153
157
  }).to_not change(duck.reload, :age)
154
158
  # puts Duck.rank(:age).collect {|duck| "#{duck.name} #{duck.age}" }
155
159
  end
@@ -183,7 +187,7 @@ describe Duck do
183
187
 
184
188
  before {
185
189
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect {|duck| duck.id }
186
- @ducks[:wingy].update_attribute :row_position, 2
190
+ @ducks[:wingy].update :row_position => 2
187
191
  }
188
192
 
189
193
  context {
@@ -210,7 +214,7 @@ describe Duck do
210
214
 
211
215
  before {
212
216
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect {|duck| duck.id }
213
- @ducks[:wingy].update_attribute :row_position, 0
217
+ @ducks[:wingy].update :row_position => 0
214
218
  }
215
219
 
216
220
  context {
@@ -250,7 +254,7 @@ describe Duck do
250
254
 
251
255
  context {
252
256
 
253
- before { @ducks[:wingy].update_attribute :row_position, (@ducks.size - 2) }
257
+ before { @ducks[:wingy].update :row_position => (@ducks.size - 2) }
254
258
 
255
259
  subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 2).instance }
256
260
 
@@ -260,7 +264,7 @@ describe Duck do
260
264
 
261
265
  context {
262
266
 
263
- before { @ducks[:wingy].update_attribute :row_position, :down }
267
+ before { @ducks[:wingy].update :row_position => :down }
264
268
 
265
269
  subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 2).instance }
266
270
 
@@ -274,7 +278,7 @@ describe Duck do
274
278
 
275
279
  before {
276
280
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect {|duck| duck.id }
277
- @ducks[:wingy].update_attribute :row_position, (@ducks.size - 1)
281
+ @ducks[:wingy].update :row_position => (@ducks.size - 1)
278
282
  }
279
283
 
280
284
  context {
@@ -315,7 +319,7 @@ describe Duck do
315
319
 
316
320
  before {
317
321
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect {|duck| duck.id }
318
- @ducks[:wingy].update_attribute :row_position, :last
322
+ @ducks[:wingy].update :row_position => :last
319
323
  }
320
324
 
321
325
  context {
@@ -356,7 +360,7 @@ describe Duck do
356
360
 
357
361
  before {
358
362
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect {|duck| duck.id }
359
- @ducks[:wingy].update_attribute :row_position, 'last'
363
+ @ducks[:wingy].update :row_position => 'last'
360
364
  }
361
365
 
362
366
  context {
@@ -399,7 +403,7 @@ describe Duck do
399
403
 
400
404
  before {
401
405
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect { |duck| duck.id }
402
- @ducks[:wingy].update_attribute :row_position, :down
406
+ @ducks[:wingy].update :row_position => :down
403
407
  }
404
408
 
405
409
  context {
@@ -426,7 +430,7 @@ describe Duck do
426
430
 
427
431
  before {
428
432
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:quacky].id).collect { |duck| duck.id }
429
- @ducks[:quacky].update_attribute :row_position, :down
433
+ @ducks[:quacky].update :row_position => :down
430
434
  }
431
435
 
432
436
  context {
@@ -451,7 +455,7 @@ describe Duck do
451
455
 
452
456
  before {
453
457
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:feathers].id).collect { |duck| duck.id }
454
- @ducks[:feathers].update_attribute :row_position, :down
458
+ @ducks[:feathers].update :row_position => :down
455
459
  }
456
460
 
457
461
  context {
@@ -480,7 +484,7 @@ describe Duck do
480
484
 
481
485
  before {
482
486
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect { |duck| duck.id }
483
- @ducks[:wingy].update_attribute :row_position, 'down'
487
+ @ducks[:wingy].update :row_position => 'down'
484
488
  }
485
489
 
486
490
  context {
@@ -507,7 +511,7 @@ describe Duck do
507
511
 
508
512
  before {
509
513
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:quacky].id).collect { |duck| duck.id }
510
- @ducks[:quacky].update_attribute :row_position, 'down'
514
+ @ducks[:quacky].update :row_position => 'down'
511
515
  }
512
516
 
513
517
  context {
@@ -532,7 +536,7 @@ describe Duck do
532
536
 
533
537
  before {
534
538
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:feathers].id).collect { |duck| duck.id }
535
- @ducks[:feathers].update_attribute :row_position, 'down'
539
+ @ducks[:feathers].update :row_position => 'down'
536
540
  }
537
541
 
538
542
  context {
@@ -561,7 +565,7 @@ describe Duck do
561
565
 
562
566
  before {
563
567
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect { |duck| duck.id }
564
- @ducks[:wingy].update_attribute :row_position, :up
568
+ @ducks[:wingy].update :row_position => :up
565
569
  }
566
570
 
567
571
  context {
@@ -588,7 +592,7 @@ describe Duck do
588
592
 
589
593
  before {
590
594
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:beaky].id).collect { |duck| duck.id }
591
- @ducks[:beaky].update_attribute :row_position, :up
595
+ @ducks[:beaky].update :row_position => :up
592
596
  }
593
597
 
594
598
  context {
@@ -613,7 +617,7 @@ describe Duck do
613
617
 
614
618
  before {
615
619
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:waddly].id).collect { |duck| duck.id }
616
- @ducks[:waddly].update_attribute :row_position, :up
620
+ @ducks[:waddly].update :row_position => :up
617
621
  }
618
622
 
619
623
  context {
@@ -641,7 +645,7 @@ describe Duck do
641
645
  Duck.where(id: @ducks[name].id).update_all(row: i)
642
646
  @ducks[name].reload
643
647
  end
644
- @ducks[:wingy].update_attribute :row_position, :up
648
+ @ducks[:wingy].update :row_position => :up
645
649
  }
646
650
 
647
651
  context {
@@ -671,7 +675,7 @@ describe Duck do
671
675
 
672
676
  before {
673
677
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect { |duck| duck.id }
674
- @ducks[:wingy].update_attribute :row_position, 'up'
678
+ @ducks[:wingy].update :row_position => 'up'
675
679
  }
676
680
 
677
681
  context {
@@ -698,7 +702,7 @@ describe Duck do
698
702
 
699
703
  before {
700
704
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:beaky].id).collect { |duck| duck.id }
701
- @ducks[:beaky].update_attribute :row_position, 'up'
705
+ @ducks[:beaky].update :row_position => 'up'
702
706
  }
703
707
 
704
708
  context {
@@ -723,7 +727,7 @@ describe Duck do
723
727
 
724
728
  before {
725
729
  @ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:waddly].id).collect { |duck| duck.id }
726
- @ducks[:waddly].update_attribute :row_position, 'up'
730
+ @ducks[:waddly].update :row_position => 'up'
727
731
  }
728
732
 
729
733
  context {
@@ -748,6 +752,71 @@ describe Duck do
748
752
 
749
753
  end
750
754
 
755
+ describe "fetching rank for an instance" do
756
+ before {
757
+ [:quacky, :feathers, :wingy, :webby, :waddly, :beaky].each_with_index do |name, i|
758
+ Duck.where(id: @ducks[name].id).update_all(row: RankedModel::MAX_RANK_VALUE - i)
759
+ @ducks[name].reload
760
+ end
761
+ }
762
+
763
+ context {
764
+ subject { Duck.find_by(id: @ducks[:beaky]).row_rank }
765
+
766
+ it { should == 0 }
767
+ }
768
+
769
+ context {
770
+ subject { Duck.find_by(id: @ducks[:wingy]).row_rank }
771
+
772
+ it { should == 3 }
773
+ }
774
+
775
+ context {
776
+ subject { Duck.find_by(id: @ducks[:quacky]).row_rank }
777
+
778
+ it { should == 5 }
779
+ }
780
+ end
781
+
782
+ describe "when moving between ponds should work when rebalancing" do
783
+ before do
784
+ [:feathers, :wingy, :webby, :waddly, :beaky].each_with_index do |name, i|
785
+ Duck.where(id: @ducks[name].id)
786
+ .update_all(age: RankedModel::MIN_RANK_VALUE + i, pond: "Boyden")
787
+ end
788
+
789
+ @ducks[:quacky].update!(age_position: 2, pond: "Boyden")
790
+ end
791
+
792
+ it 'rebalances ranks correctly' do
793
+ expect(@ducks[:feathers].reload.age_rank).to eq 0
794
+ expect(@ducks[:quacky].reload.age_rank).to eq 2
795
+ expect(@ducks[:beaky].reload.age_rank).to eq 5
796
+ end
797
+
798
+ context 'when attempting to update position to a non-unique value' do
799
+ before do
800
+ @duck_one = Duck.create(landing_order: RankedModel::MIN_RANK_VALUE,
801
+ lake_id: 42, flock_id: 42)
802
+ # Duck one's landing order will be rebalanced to -715_827_883.
803
+ # Given a unique index on [:landing_order, :lake_id, :flock_id] we
804
+ # verify that the operation succeeds despite the value already being
805
+ # occupied by duck two.
806
+ @duck_two = Duck.create(landing_order: -715_827_883,
807
+ lake_id: 42, flock_id: 42)
808
+ end
809
+
810
+ it 'rebalances ranks correctly' do
811
+ @ducks[:quacky].update!(landing_order_position: :first,
812
+ lake_id: 42, flock_id: 42)
813
+ expect(@ducks[:quacky].reload.landing_order_rank).to eq 0
814
+ expect(@duck_one.reload.landing_order_rank).to eq 1
815
+ expect(@duck_two.reload.landing_order_rank).to eq 2
816
+ end
817
+ end
818
+ end
819
+
751
820
  end
752
821
 
753
822
  describe Duck do
@@ -781,7 +850,7 @@ describe Duck do
781
850
  }
782
851
  @ducks.each { |name, duck|
783
852
  duck.reload
784
- duck.update_attribute :landing_order_position, 0
853
+ duck.update :landing_order_position => 0
785
854
  duck.save!
786
855
  }
787
856
  @ducks.each {|name, duck| duck.reload }
@@ -790,8 +859,8 @@ describe Duck do
790
859
  describe "sorting by landing_order" do
791
860
 
792
861
  before {
793
- @ducks[:quacky].update_attribute :landing_order_position, 0
794
- @ducks[:wingy].update_attribute :landing_order_position, 1
862
+ @ducks[:quacky].update :landing_order_position => 0
863
+ @ducks[:wingy].update :landing_order_position => 1
795
864
  }
796
865
 
797
866
  subject { Duck.in_lake_and_flock(0,0).rank(:landing_order).to_a }
@@ -815,10 +884,10 @@ describe Duck do
815
884
 
816
885
  @previous_ranks = @untouchable_ranks.call
817
886
 
818
- @ducks[:quacky].update_attribute :landing_order_position, 0
819
- @ducks[:wingy].update_attribute :landing_order_position, 1
820
- @ducks[:feathers].update_attribute :landing_order_position, 0
821
- @ducks[:wingy].update_attribute :landing_order_position, 1
887
+ @ducks[:quacky].update :landing_order_position => 0
888
+ @ducks[:wingy].update :landing_order_position => 1
889
+ @ducks[:feathers].update :landing_order_position => 0
890
+ @ducks[:wingy].update :landing_order_position => 1
822
891
  }
823
892
 
824
893
  subject { @untouchable_ranks.call }