ranked-model-rails2 0.4.0

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.
@@ -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