ranked-model-rails2 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rbenv-vars +1 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +82 -0
- data/Gemfile +32 -0
- data/LICENSE +19 -0
- data/Rakefile +7 -0
- data/Readme.mkd +205 -0
- data/lib/ranked-model.rb +69 -0
- data/lib/ranked-model/railtie.rb +14 -0
- data/lib/ranked-model/ranker.rb +327 -0
- data/lib/ranked-model/version.rb +3 -0
- data/rails/init.rb +1 -0
- data/ranked-model-rails2.gemspec +29 -0
- data/spec/duck-model/duck_spec.rb +770 -0
- data/spec/duck-model/lots_of_ducks_spec.rb +169 -0
- data/spec/duck-model/wrong_ducks_spec.rb +36 -0
- data/spec/ego-model/ego_spec.rb +36 -0
- data/spec/player-model/records_already_exist_spec.rb +22 -0
- data/spec/ranked-model/ranker_spec.rb +64 -0
- data/spec/ranked-model/version_spec.rb +7 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/sti-model/element_spec.rb +122 -0
- data/spec/sti-model/vehicle_spec.rb +75 -0
- data/spec/support/active_record.rb +150 -0
- data/spec/support/database.yml +30 -0
- metadata +171 -0
@@ -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
|
data/rails/init.rb
ADDED
@@ -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
|