ranked-model-rails2 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ require 'ranked-model'
2
+ require 'rails'
3
+
4
+ module RankedModel
5
+
6
+ class Railtie < Rails::Railtie
7
+
8
+ initializer "ranked-model.initialize" do |app|
9
+
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,327 @@
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([%Q{#{ranker.column} = ?}, value])
70
+ end
71
+
72
+ def position
73
+ instance.send "#{ranker.name}_position"
74
+ end
75
+
76
+ def rank
77
+ instance.send "#{ranker.column}"
78
+ end
79
+
80
+ def current_at_position _pos
81
+ if (ordered_instance = finder.offset(_pos).first)
82
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
83
+ end
84
+ end
85
+
86
+ def has_rank?
87
+ !rank.nil?
88
+ end
89
+
90
+ private
91
+
92
+ def instance_class
93
+ ranker.class_name.nil? ? instance.class : ranker.class_name.constantize
94
+ end
95
+
96
+ def position_at value
97
+ instance.send "#{ranker.name}_position=", value
98
+ update_index_from_position
99
+ end
100
+
101
+ def rank_at value
102
+ instance.send "#{ranker.column}=", value
103
+ end
104
+
105
+ def rank_changed?
106
+ instance.send "#{ranker.column}_changed?"
107
+ end
108
+
109
+ def new_record?
110
+ instance.new_record?
111
+ end
112
+
113
+ def update_index_from_position
114
+ case position
115
+ when :first, 'first'
116
+ if current_first && current_first.rank
117
+ rank_at( ( ( RankedModel::MIN_RANK_VALUE - current_first.rank ).to_f / 2 ).ceil + current_first.rank)
118
+ else
119
+ position_at :middle
120
+ end
121
+ when :last, 'last'
122
+ if current_last && current_last.rank
123
+ rank_at( ( ( RankedModel::MAX_RANK_VALUE - current_last.rank ).to_f / 2 ).ceil + current_last.rank )
124
+ else
125
+ position_at :middle
126
+ end
127
+ when :middle, 'middle'
128
+ rank_at( ( ( RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE ).to_f / 2 ).ceil + RankedModel::MIN_RANK_VALUE )
129
+ when :down, 'down'
130
+ neighbors = find_next_two(rank)
131
+ if neighbors[:lower]
132
+ min = neighbors[:lower].rank
133
+ max = neighbors[:upper] ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE
134
+ rank_at( ( ( max - min ).to_f / 2 ).ceil + min )
135
+ end
136
+ when :up, 'up'
137
+ neighbors = find_previous_two(rank)
138
+ if neighbors[:upper]
139
+ max = neighbors[:upper].rank
140
+ min = neighbors[:lower] ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE
141
+ rank_at( ( ( max - min ).to_f / 2 ).ceil + min )
142
+ end
143
+ when String
144
+ position_at position.to_i
145
+ when 0
146
+ position_at :first
147
+ when Integer
148
+ neighbors = neighbors_at_position(position)
149
+ min = ((neighbors[:lower] && neighbors[:lower].has_rank?) ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE)
150
+ max = ((neighbors[:upper] && neighbors[:upper].has_rank?) ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE)
151
+ rank_at( ( ( max - min ).to_f / 2 ).ceil + min )
152
+ when NilClass
153
+ if !rank
154
+ position_at :last
155
+ end
156
+ end
157
+ end
158
+
159
+ def assure_unique_position
160
+ if ( new_record? || rank_changed? )
161
+ unless rank
162
+ rank_at( RankedModel::MAX_RANK_VALUE )
163
+ end
164
+
165
+ if (rank > RankedModel::MAX_RANK_VALUE) || current_at_rank(rank)
166
+ rearrange_ranks
167
+ end
168
+ end
169
+ end
170
+
171
+ def rearrange_ranks
172
+ _scope = finder
173
+ unless instance.id.nil?
174
+ # Never update ourself, shift others around us.
175
+ _scope = _scope.where(%Q{#{instance_class.q(instance_class.primary_key)} != ?}, instance.id)
176
+ end
177
+ if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
178
+ _scope.
179
+ where(%Q{#{instance_class.q(ranker.column)} <= ?}, rank).
180
+ update_all( %Q{#{ranker.column} = #{ranker.column} - 1} )
181
+ elsif current_last.rank && current_last.rank < (RankedModel::MAX_RANK_VALUE - 1) && rank < current_last.rank
182
+ _scope.
183
+ where(%Q{#{instance_class.q(ranker.column)} >= ?}, rank).
184
+ update_all( %Q{#{ranker.column} = #{ranker.column} + 1} )
185
+ elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank
186
+ _scope.
187
+ where(%Q{#{instance_class.q(ranker.column)} < ?}, rank).
188
+ update_all( %Q{#{ranker.column} = #{ranker.column} - 1} )
189
+ rank_at( rank - 1 )
190
+ else
191
+ rebalance_ranks
192
+ end
193
+ end
194
+
195
+ def rebalance_ranks
196
+ total = current_order.size + 2
197
+ has_set_self = false
198
+ total.times do |index|
199
+ next if index == 0 || index == total
200
+ rank_value = ((((RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE).to_f / total) * index ).ceil + RankedModel::MIN_RANK_VALUE)
201
+ index = index - 1
202
+ if has_set_self
203
+ index = index - 1
204
+ else
205
+ if !current_order[index] ||
206
+ ( !current_order[index].rank.nil? &&
207
+ current_order[index].rank >= rank )
208
+ rank_at rank_value
209
+ has_set_self = true
210
+ next
211
+ end
212
+ end
213
+ current_order[index].update_rank! rank_value
214
+ end
215
+ end
216
+
217
+ def finder(order = :asc)
218
+ @finder ||= begin
219
+ _finder = instance_class
220
+ columns = [instance_class.primary_key, ranker.column].map(&:to_sym)
221
+ if ranker.scope
222
+ _finder = _finder.send ranker.scope
223
+ end
224
+ case ranker.with_same
225
+ when Symbol
226
+ columns << ranker.with_same
227
+ _finder = _finder.where \
228
+ ranker.with_same => instance["#{ranker.with_same}"]
229
+ when Array
230
+ columns += ranker.with_same.map(&:to_sym)
231
+ ranker.with_same.each do |c|
232
+ _finder = _finder.where(c.to_sym => instance["#{c}"])
233
+ end
234
+ end
235
+ if !new_record?
236
+ _finder = _finder.where \
237
+ %Q{#{instance_class.q(instance_class.primary_key)} != ?}, instance.id
238
+ end
239
+ _finder.order("#{instance_class.q(ranker.column)} #{order.to_s.upcase}").select(columns)
240
+ end
241
+ end
242
+
243
+ def current_order
244
+ @current_order ||= begin
245
+ finder.collect { |ordered_instance|
246
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
247
+ }
248
+ end
249
+ end
250
+
251
+ def current_first
252
+ @current_first ||= begin
253
+ if (ordered_instance = finder.first)
254
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
255
+ end
256
+ end
257
+ end
258
+
259
+ def current_last
260
+ @current_last ||= begin
261
+ if (ordered_instance = finder.
262
+ reverse.
263
+ first)
264
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
265
+ end
266
+ end
267
+ end
268
+
269
+ def current_at_rank _rank
270
+ if (ordered_instance = finder.
271
+ except( :order ).
272
+ where( ranker.column => _rank ).
273
+ first)
274
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
275
+ end
276
+ end
277
+
278
+ def neighbors_at_position _pos
279
+ if _pos > 0
280
+ if (ordered_instances = finder.offset(_pos-1).limit(2).to_a)
281
+ if ordered_instances[1]
282
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
283
+ :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
284
+ elsif ordered_instances[0]
285
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
286
+ else
287
+ { :lower => current_last }
288
+ end
289
+ end
290
+ else
291
+ if (ordered_instance = finder.first)
292
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instance ) }
293
+ else
294
+ {}
295
+ end
296
+ end
297
+ end
298
+
299
+ def find_next_two _rank
300
+ ordered_instances = finder.where(%Q{#{instance_class.q(ranker.column)} > ?}, _rank).limit(2)
301
+ if ordered_instances[1]
302
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
303
+ :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
304
+ elsif ordered_instances[0]
305
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
306
+ else
307
+ {}
308
+ end
309
+ end
310
+
311
+ def find_previous_two _rank
312
+ ordered_instances = finder(:desc).where(%Q{#{instance_class.q(ranker.column)} < ?}, _rank).limit(2)
313
+ if ordered_instances[1]
314
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
315
+ :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
316
+ elsif ordered_instances[0]
317
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
318
+ else
319
+ {}
320
+ end
321
+ end
322
+
323
+ end
324
+
325
+ end
326
+
327
+ end
@@ -0,0 +1,3 @@
1
+ module RankedModel
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1 @@
1
+ require 'ranked-model/railtie'
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "ranked-model/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ranked-model-rails2"
7
+ s.version = RankedModel::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["ThoughtWorks Studios"]
10
+ s.email = ["mingle-dev@thoughtworks.com"]
11
+ s.homepage = "https://github.com/ThoughtWorksStudios/ranked-model-rails2"
12
+ s.summary = %q{An acts_as_sortable replacement built for Rails 2}
13
+ s.description = %q{ranked-model-rails2 is a modern row sorting library built for Rails 2, backported from ranked-model. It uses fake_arel and is better optimized than most other libraries.}
14
+ s.license = 'MIT'
15
+
16
+ s.add_dependency "activerecord", "~> 2.3.18"
17
+ s.add_dependency "fake_arel", "~> 1.5.1"
18
+ s.add_development_dependency "rspec", "~> 2.13.0"
19
+ s.add_development_dependency "sqlite3", "~> 1.3.7"
20
+ s.add_development_dependency "genspec", "~> 0.2.8"
21
+ s.add_development_dependency "mocha", "~> 0.14.0"
22
+ s.add_development_dependency "database_cleaner", "~> 1.2.0"
23
+ s.add_development_dependency "rake", "~> 10.1.0"
24
+
25
+ s.files = `git ls-files`.split("\n")
26
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
+ s.require_paths = ["lib"]
29
+ end
@@ -0,0 +1,770 @@
1
+ require 'spec_helper'
2
+
3
+ describe Duck do
4
+
5
+ before {
6
+ @duck = Duck.new
7
+ }
8
+
9
+ subject { @duck }
10
+
11
+ it { subject.respond_to?(:row_position).should be_true }
12
+ it { subject.respond_to?(:row_position=).should be_true }
13
+ it { subject.respond_to?(:size_position).should be_true }
14
+ it { subject.respond_to?(:size_position=).should be_true }
15
+ it { subject.respond_to?(:age_position).should be_true }
16
+ it { subject.respond_to?(:age_position=).should be_true }
17
+ it { subject.respond_to?(:landing_order_position).should be_true }
18
+ it { subject.respond_to?(:landing_order_position=).should be_true }
19
+
20
+ end
21
+
22
+ describe Duck do
23
+
24
+ before {
25
+ @ducks = {
26
+ :quacky => Duck.create(
27
+ :name => 'Quacky',
28
+ :pond => 'Shin' ),
29
+ :feathers => Duck.create(
30
+ :name => 'Feathers',
31
+ :pond => 'Shin' ),
32
+ :wingy => Duck.create(
33
+ :name => 'Wingy',
34
+ :pond => 'Shin' ),
35
+ :webby => Duck.create(
36
+ :name => 'Webby',
37
+ :pond => 'Boyden' ),
38
+ :waddly => Duck.create(
39
+ :name => 'Waddly',
40
+ :pond => 'Meddybemps' ),
41
+ :beaky => Duck.create(
42
+ :name => 'Beaky',
43
+ :pond => 'Great Moose' )
44
+ }
45
+ @ducks.each { |name, duck|
46
+ duck.reload
47
+ duck.update_attribute :row_position, 0
48
+ duck.update_attribute :size_position, 0
49
+ duck.update_attribute :age_position, 0
50
+ duck.save!
51
+ }
52
+ @ducks.each {|name, duck| duck.reload }
53
+ }
54
+
55
+ describe "sorting by size on in_shin_pond" do
56
+
57
+ before {
58
+ @ducks[:quacky].update_attribute :size_position, 0
59
+ @ducks[:wingy].update_attribute :size_position, 2
60
+ }
61
+
62
+ subject { Duck.in_shin_pond.rank(:size).to_a }
63
+
64
+ its(:size) { should == 3 }
65
+
66
+ its(:first) { should == @ducks[:quacky] }
67
+
68
+ its(:last) { should == @ducks[:wingy] }
69
+
70
+ end
71
+
72
+ describe "sorting by age on Shin pond" do
73
+
74
+ before {
75
+ @ducks[:feathers].update_attribute :age_position, 0
76
+ @ducks[:wingy].update_attribute :age_position, 0
77
+ }
78
+
79
+ subject { Duck.where(:pond => 'Shin').rank(:age).to_a }
80
+
81
+ its(:size) { should == 3 }
82
+
83
+ its(:first) { should == @ducks[:wingy] }
84
+
85
+ its(:last) { should == @ducks[:quacky] }
86
+
87
+ end
88
+
89
+ describe "sorting by row" do
90
+
91
+ 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
+ }
97
+
98
+ subject { Duck.rank(:row).to_a }
99
+
100
+ its(:size) { should == 6 }
101
+
102
+ its(:first) { should == @ducks[:beaky] }
103
+
104
+ its(:last) { should == @ducks[:wingy] }
105
+
106
+ end
107
+
108
+ describe "mixed sorting by" do
109
+
110
+ 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
118
+ }
119
+
120
+ describe "row" do
121
+
122
+ subject { Duck.rank(:row).to_a }
123
+
124
+ its(:size) { should == 6 }
125
+
126
+ its(:first) { should == @ducks[:beaky] }
127
+
128
+ its(:last) { should == @ducks[:webby] }
129
+
130
+ end
131
+
132
+ describe "row" do
133
+
134
+ subject { Duck.in_shin_pond.rank(:size).to_a }
135
+
136
+ its(:size) { should == 3 }
137
+
138
+ its(:first) { should == @ducks[:quacky] }
139
+
140
+ its(:last) { should == @ducks[:feathers] }
141
+
142
+ end
143
+
144
+ end
145
+
146
+ describe "changing an unrelated attribute" do
147
+
148
+ it "doesn't change ranking" do
149
+ # puts Duck.rank(:age).collect {|duck| "#{duck.name} #{duck.age}" }
150
+ duck = Duck.rank(:age)[2]
151
+ lambda {
152
+ duck.update_attribute :name, 'New Name'
153
+ }.should_not change(duck.reload, :age)
154
+ # puts Duck.rank(:age).collect {|duck| "#{duck.name} #{duck.age}" }
155
+ end
156
+
157
+ end
158
+
159
+ describe "changing a related attribute" do
160
+
161
+ it "marks record as changed" do
162
+ duck = Duck.rank(:age)[2]
163
+ duck.age_position = 1
164
+ duck.changed?.should be_true
165
+ end
166
+
167
+ end
168
+
169
+ describe "setting only truly values" do
170
+
171
+ subject { Duck.rank(:age).first }
172
+
173
+ it "doesnt set empty string" do
174
+ subject.age_position = ''
175
+ subject.age_position.should be_nil
176
+ end
177
+
178
+ end
179
+
180
+ describe "setting and fetching by positioning" do
181
+
182
+ describe "in the middle" do
183
+
184
+ before {
185
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect {|duck| duck.id }
186
+ @ducks[:wingy].update_attribute :row_position, 2
187
+ }
188
+
189
+ context {
190
+
191
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(2).instance }
192
+
193
+ its(:id) { should == @ducks[:wingy].id }
194
+
195
+ }
196
+
197
+ context {
198
+
199
+ subject { Duck.rank(:row).collect {|duck| duck.id } }
200
+
201
+ it { subject[0..1].should == @ordered[0..1] }
202
+
203
+ it { subject[3..subject.length].should == @ordered[2..@ordered.length] }
204
+
205
+ }
206
+
207
+ end
208
+
209
+ describe "at the start" do
210
+
211
+ before {
212
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect {|duck| duck.id }
213
+ @ducks[:wingy].update_attribute :row_position, 0
214
+ }
215
+
216
+ context {
217
+
218
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(0).instance }
219
+
220
+ its(:id) { should == @ducks[:wingy].id }
221
+
222
+ }
223
+
224
+ context {
225
+
226
+ subject { Duck.ranker(:row).with(Duck.new).instance_eval { current_first }.instance }
227
+
228
+ its(:id) { should == @ducks[:wingy].id }
229
+
230
+ }
231
+
232
+ context {
233
+
234
+ subject { Duck.rank(:row).collect {|duck| duck.id } }
235
+
236
+ it { subject[1..subject.length].should == @ordered }
237
+
238
+ }
239
+
240
+ end
241
+
242
+ describe "at the end" do
243
+
244
+ before {
245
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect {|duck| duck.id }
246
+ @ducks[:wingy].update_attribute :row_position, (@ducks.size - 1)
247
+ }
248
+
249
+ context {
250
+
251
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 1).instance }
252
+
253
+ its(:id) { should == @ducks[:wingy].id }
254
+
255
+ }
256
+
257
+ context {
258
+
259
+ subject { Duck.rank(:row).last }
260
+
261
+ its(:id) { should == @ducks[:wingy].id }
262
+
263
+ }
264
+
265
+ context {
266
+
267
+ subject { Duck.ranker(:row).with(Duck.new).instance_eval { current_last }.instance }
268
+
269
+ its(:id) { should == @ducks[:wingy].id }
270
+
271
+ }
272
+
273
+ context {
274
+
275
+ subject { Duck.rank(:row).collect {|duck| duck.id } }
276
+
277
+ it { subject[0..-2].should == @ordered }
278
+
279
+ }
280
+
281
+ end
282
+
283
+ describe "at the end with symbol" do
284
+
285
+ before {
286
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect {|duck| duck.id }
287
+ @ducks[:wingy].update_attribute :row_position, :last
288
+ }
289
+
290
+ context {
291
+
292
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 1).instance }
293
+
294
+ its(:id) { should == @ducks[:wingy].id }
295
+
296
+ }
297
+
298
+ context {
299
+
300
+ subject { Duck.rank(:row).last }
301
+
302
+ its(:id) { should == @ducks[:wingy].id }
303
+
304
+ }
305
+
306
+ context {
307
+
308
+ subject { Duck.ranker(:row).with(Duck.new).instance_eval { current_last }.instance }
309
+
310
+ its(:id) { should == @ducks[:wingy].id }
311
+
312
+ }
313
+
314
+ context {
315
+
316
+ subject { Duck.rank(:row).collect {|duck| duck.id } }
317
+
318
+ it { subject[0..-2].should == @ordered }
319
+
320
+ }
321
+
322
+ end
323
+
324
+ describe "at the end with string" do
325
+
326
+ before {
327
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect {|duck| duck.id }
328
+ @ducks[:wingy].update_attribute :row_position, 'last'
329
+ }
330
+
331
+ context {
332
+
333
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 1).instance }
334
+
335
+ its(:id) { should == @ducks[:wingy].id }
336
+
337
+ }
338
+
339
+ context {
340
+
341
+ subject { Duck.rank(:row).last }
342
+
343
+ its(:id) { should == @ducks[:wingy].id }
344
+
345
+ }
346
+
347
+ context {
348
+
349
+ subject { Duck.ranker(:row).with(Duck.new).instance_eval { current_last }.instance }
350
+
351
+ its(:id) { should == @ducks[:wingy].id }
352
+
353
+ }
354
+
355
+ context {
356
+
357
+ subject { Duck.rank(:row).collect {|duck| duck.id } }
358
+
359
+ it { subject[0..-2].should == @ordered }
360
+
361
+ }
362
+
363
+ end
364
+
365
+ describe "down with symbol" do
366
+
367
+ context "when in the middle" do
368
+
369
+ before {
370
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect { |duck| duck.id }
371
+ @ducks[:wingy].update_attribute :row_position, :down
372
+ }
373
+
374
+ context {
375
+
376
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(4).instance }
377
+
378
+ its(:id) { should == @ducks[:wingy].id }
379
+
380
+ }
381
+
382
+ context {
383
+
384
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
385
+
386
+ it { subject[0..3].should == @ordered[0..3] }
387
+
388
+ it { subject[5..subject.length].should == @ordered[4..@ordered.length] }
389
+
390
+ }
391
+
392
+ end
393
+
394
+ context "when last" do
395
+
396
+ before {
397
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:quacky].id).collect { |duck| duck.id }
398
+ @ducks[:quacky].update_attribute :row_position, :down
399
+ }
400
+
401
+ context {
402
+
403
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 1).instance }
404
+
405
+ its(:id) { should == @ducks[:quacky].id }
406
+
407
+ }
408
+
409
+ context {
410
+
411
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
412
+
413
+ it { subject[0..-2].should eq(@ordered) }
414
+
415
+ }
416
+
417
+ end
418
+
419
+ context "when second last" do
420
+
421
+ before {
422
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:feathers].id).collect { |duck| duck.id }
423
+ @ducks[:feathers].update_attribute :row_position, :down
424
+ }
425
+
426
+ context {
427
+
428
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 1).instance }
429
+
430
+ its(:id) { should == @ducks[:feathers].id }
431
+
432
+ }
433
+
434
+ context {
435
+
436
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
437
+
438
+ it { subject[0..-2].should eq(@ordered) }
439
+
440
+ }
441
+
442
+ end
443
+
444
+ end
445
+
446
+ describe "down with string" do
447
+
448
+ context "when in the middle" do
449
+
450
+ before {
451
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect { |duck| duck.id }
452
+ @ducks[:wingy].update_attribute :row_position, 'down'
453
+ }
454
+
455
+ context {
456
+
457
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(4).instance }
458
+
459
+ its(:id) { should == @ducks[:wingy].id }
460
+
461
+ }
462
+
463
+ context {
464
+
465
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
466
+
467
+ it { subject[0..3].should == @ordered[0..3] }
468
+
469
+ it { subject[5..subject.length].should == @ordered[4..@ordered.length] }
470
+
471
+ }
472
+
473
+ end
474
+
475
+ context "when last" do
476
+
477
+ before {
478
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:quacky].id).collect { |duck| duck.id }
479
+ @ducks[:quacky].update_attribute :row_position, 'down'
480
+ }
481
+
482
+ context {
483
+
484
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 1).instance }
485
+
486
+ its(:id) { should == @ducks[:quacky].id }
487
+
488
+ }
489
+
490
+ context {
491
+
492
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
493
+
494
+ it { subject[0..-2].should eq(@ordered) }
495
+
496
+ }
497
+
498
+ end
499
+
500
+ context "when second last" do
501
+
502
+ before {
503
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:feathers].id).collect { |duck| duck.id }
504
+ @ducks[:feathers].update_attribute :row_position, 'down'
505
+ }
506
+
507
+ context {
508
+
509
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 1).instance }
510
+
511
+ its(:id) { should == @ducks[:feathers].id }
512
+
513
+ }
514
+
515
+ context {
516
+
517
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
518
+
519
+ it { subject[0..-2].should eq(@ordered) }
520
+
521
+ }
522
+
523
+ end
524
+
525
+ end
526
+
527
+ describe "up with symbol" do
528
+
529
+ context "when in the middle" do
530
+
531
+ before {
532
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect { |duck| duck.id }
533
+ @ducks[:wingy].update_attribute :row_position, :up
534
+ }
535
+
536
+ context {
537
+
538
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(2).instance }
539
+
540
+ its(:id) { should == @ducks[:wingy].id }
541
+
542
+ }
543
+
544
+ context {
545
+
546
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
547
+
548
+ it { subject[0..1].should == @ordered[0..1] }
549
+
550
+ it { subject[3..subject.length].should == @ordered[2..@ordered.length] }
551
+
552
+ }
553
+
554
+ end
555
+
556
+ context "when first" do
557
+
558
+ before {
559
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:beaky].id).collect { |duck| duck.id }
560
+ @ducks[:beaky].update_attribute :row_position, :up
561
+ }
562
+
563
+ context {
564
+
565
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(0).instance }
566
+
567
+ its(:id) { should == @ducks[:beaky].id }
568
+
569
+ }
570
+
571
+ context {
572
+
573
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
574
+
575
+ it { subject[1..subject.length].should eq(@ordered) }
576
+
577
+ }
578
+
579
+ end
580
+
581
+ context "when second" do
582
+
583
+ before {
584
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:waddly].id).collect { |duck| duck.id }
585
+ @ducks[:waddly].update_attribute :row_position, :up
586
+ }
587
+
588
+ context {
589
+
590
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(0).instance }
591
+
592
+ its(:id) { should == @ducks[:waddly].id }
593
+
594
+ }
595
+
596
+ context {
597
+
598
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
599
+
600
+ it { subject[1..subject.length].should eq(@ordered) }
601
+
602
+ }
603
+
604
+ end
605
+
606
+ end
607
+
608
+ describe "up with string" do
609
+
610
+ context "when in the middle" do
611
+
612
+ before {
613
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:wingy].id).collect { |duck| duck.id }
614
+ @ducks[:wingy].update_attribute :row_position, 'up'
615
+ }
616
+
617
+ context {
618
+
619
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(2).instance }
620
+
621
+ its(:id) { should == @ducks[:wingy].id }
622
+
623
+ }
624
+
625
+ context {
626
+
627
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
628
+
629
+ it { subject[0..1].should == @ordered[0..1] }
630
+
631
+ it { subject[3..subject.length].should == @ordered[2..@ordered.length] }
632
+
633
+ }
634
+
635
+ end
636
+
637
+ context "when first" do
638
+
639
+ before {
640
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:beaky].id).collect { |duck| duck.id }
641
+ @ducks[:beaky].update_attribute :row_position, 'up'
642
+ }
643
+
644
+ context {
645
+
646
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(0).instance }
647
+
648
+ its(:id) { should == @ducks[:beaky].id }
649
+
650
+ }
651
+
652
+ context {
653
+
654
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
655
+
656
+ it { subject[1..subject.length].should eq(@ordered) }
657
+
658
+ }
659
+
660
+ end
661
+
662
+ context "when second" do
663
+
664
+ before {
665
+ @ordered = Duck.rank(:row).where("id != ?", @ducks[:waddly].id).collect { |duck| duck.id }
666
+ @ducks[:waddly].update_attribute :row_position, 'up'
667
+ }
668
+
669
+ context {
670
+
671
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(0).instance }
672
+
673
+ its(:id) { should == @ducks[:waddly].id }
674
+
675
+ }
676
+
677
+ context {
678
+
679
+ subject { Duck.rank(:row).collect { |duck| duck.id } }
680
+
681
+ it { subject[1..subject.length].should eq(@ordered) }
682
+
683
+ }
684
+
685
+ end
686
+
687
+ end
688
+
689
+ end
690
+
691
+ end
692
+
693
+ describe Duck do
694
+
695
+ before {
696
+ @ducks = {
697
+ :quacky => Duck.create(
698
+ :name => 'Quacky',
699
+ :lake_id => 0,
700
+ :flock_id => 0 ),
701
+ :feathers => Duck.create(
702
+ :name => 'Feathers',
703
+ :lake_id => 0,
704
+ :flock_id => 0 ),
705
+ :wingy => Duck.create(
706
+ :name => 'Wingy',
707
+ :lake_id => 0,
708
+ :flock_id => 0 ),
709
+ :webby => Duck.create(
710
+ :name => 'Webby',
711
+ :lake_id => 1,
712
+ :flock_id => 1 ),
713
+ :waddly => Duck.create(
714
+ :name => 'Waddly',
715
+ :lake_id => 1,
716
+ :flock_id => 0 ),
717
+ :beaky => Duck.create(
718
+ :name => 'Beaky',
719
+ :lake_id => 0,
720
+ :flock_id => 1 ),
721
+ }
722
+ @ducks.each { |name, duck|
723
+ duck.reload
724
+ duck.update_attribute :landing_order_position, 0
725
+ duck.save!
726
+ }
727
+ @ducks.each {|name, duck| duck.reload }
728
+ }
729
+
730
+ describe "sorting by landing_order" do
731
+
732
+ before {
733
+ @ducks[:quacky].update_attribute :landing_order_position, 0
734
+ @ducks[:wingy].update_attribute :landing_order_position, 1
735
+ }
736
+
737
+ subject { Duck.in_lake_and_flock(0,0).rank(:landing_order).to_a }
738
+
739
+ its(:size) { should == 3 }
740
+
741
+ its(:first) { should == @ducks[:quacky] }
742
+
743
+ its(:last) { should == @ducks[:feathers] }
744
+
745
+ end
746
+
747
+ describe "sorting by landing_order doesn't touch other items" do
748
+
749
+ before {
750
+ @untouchable_ranks = lambda {
751
+ [:webby, :waddly, :beaky].inject([]) do |ranks, untouchable_duck|
752
+ ranks << @ducks[untouchable_duck].landing_order
753
+ end
754
+ }
755
+
756
+ @previous_ranks = @untouchable_ranks.call
757
+
758
+ @ducks[:quacky].update_attribute :landing_order_position, 0
759
+ @ducks[:wingy].update_attribute :landing_order_position, 1
760
+ @ducks[:feathers].update_attribute :landing_order_position, 0
761
+ @ducks[:wingy].update_attribute :landing_order_position, 1
762
+ }
763
+
764
+ subject { @untouchable_ranks.call }
765
+
766
+ it { should == @previous_ranks }
767
+
768
+ end
769
+
770
+ end