acts_as_list 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/README.md +37 -0
- data/lib/acts_as_list/active_record/acts/list.rb +60 -10
- data/lib/acts_as_list/version.rb +1 -1
- data/test/helper.rb +2 -0
- data/test/shared.rb +7 -0
- data/test/shared_array_scope_list.rb +156 -0
- data/test/shared_list.rb +233 -0
- data/test/shared_list_sub.rb +102 -0
- data/test/shared_zero_based.rb +86 -0
- data/test/test_list.rb +111 -527
- metadata +65 -89
data/.gitignore
CHANGED
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# ActsAsList
|
2
|
+
|
3
|
+
## Description
|
4
|
+
|
5
|
+
This `acts_as extension` provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a `position` column defined as an integer on the mapped database table.
|
6
|
+
|
7
|
+
|
8
|
+
## Example
|
9
|
+
|
10
|
+
class TodoList < ActiveRecord::Base
|
11
|
+
has_many :todo_items, :order => "position"
|
12
|
+
end
|
13
|
+
|
14
|
+
class TodoItem < ActiveRecord::Base
|
15
|
+
belongs_to :todo_list
|
16
|
+
acts_as_list :scope => :todo_list
|
17
|
+
end
|
18
|
+
|
19
|
+
todo_list.first.move_to_bottom
|
20
|
+
todo_list.last.move_higher
|
21
|
+
|
22
|
+
## Notes
|
23
|
+
If the `position` column has a default value, then there is a slight change in behavior, i.e if you have 4 items in the list, and you insert 1, with a default position 0, it would be pushed to the bottom of the list. Please look at the tests for this and some recent pull requests for discussions related to this.
|
24
|
+
|
25
|
+
## Contributing to `acts_as_list`
|
26
|
+
|
27
|
+
- Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
28
|
+
- Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
29
|
+
- Fork the project
|
30
|
+
- Start a feature/bugfix branch
|
31
|
+
- Commit and push until you are happy with your contribution
|
32
|
+
- Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
33
|
+
- Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
34
|
+
|
35
|
+
## Copyright
|
36
|
+
|
37
|
+
Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
# to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
|
32
32
|
# Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
|
33
33
|
# * +top_of_list+ - defines the integer used for the top of the list. Defaults to 1. Use 0 to make the collection
|
34
|
-
# act more
|
34
|
+
# act more like an array in its indexing.
|
35
35
|
def acts_as_list(options = {})
|
36
36
|
configuration = { :column => "position", :scope => "1 = 1", :top_of_list => 1}
|
37
37
|
configuration.update(options) if options.is_a?(Hash)
|
@@ -74,8 +74,8 @@ module ActiveRecord
|
|
74
74
|
|
75
75
|
#{scope_condition_method}
|
76
76
|
|
77
|
-
|
78
|
-
|
77
|
+
after_destroy :decrement_positions_on_lower_items
|
78
|
+
before_validation :add_to_list_bottom, :on => :create
|
79
79
|
EOV
|
80
80
|
end
|
81
81
|
end
|
@@ -180,7 +180,19 @@ module ActiveRecord
|
|
180
180
|
|
181
181
|
# Test if this record is in a list
|
182
182
|
def in_list?
|
183
|
-
!
|
183
|
+
!not_in_list?
|
184
|
+
end
|
185
|
+
|
186
|
+
def not_in_list?
|
187
|
+
send(position_column).nil?
|
188
|
+
end
|
189
|
+
|
190
|
+
def default_position
|
191
|
+
acts_as_list_class.columns_hash[position_column.to_s].default
|
192
|
+
end
|
193
|
+
|
194
|
+
def default_position?
|
195
|
+
default_position == send(position_column)
|
184
196
|
end
|
185
197
|
|
186
198
|
private
|
@@ -189,7 +201,7 @@ module ActiveRecord
|
|
189
201
|
end
|
190
202
|
|
191
203
|
def add_to_list_bottom
|
192
|
-
if
|
204
|
+
if not_in_list? || default_position?
|
193
205
|
self[position_column] = bottom_position_in_list.to_i + 1
|
194
206
|
else
|
195
207
|
increment_positions_on_lower_items(self[position_column])
|
@@ -210,7 +222,7 @@ module ActiveRecord
|
|
210
222
|
def bottom_item(except = nil)
|
211
223
|
conditions = scope_condition
|
212
224
|
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
|
213
|
-
acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
|
225
|
+
acts_as_list_class.unscoped.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
|
214
226
|
end
|
215
227
|
|
216
228
|
# Forces item to assume the bottom position in the list.
|
@@ -231,10 +243,11 @@ module ActiveRecord
|
|
231
243
|
end
|
232
244
|
|
233
245
|
# This has the effect of moving all the lower items up one.
|
234
|
-
def decrement_positions_on_lower_items
|
246
|
+
def decrement_positions_on_lower_items(position=nil)
|
235
247
|
return unless in_list?
|
248
|
+
position ||= send(position_column).to_i
|
236
249
|
acts_as_list_class.update_all(
|
237
|
-
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{
|
250
|
+
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{position}"
|
238
251
|
)
|
239
252
|
end
|
240
253
|
|
@@ -260,11 +273,48 @@ module ActiveRecord
|
|
260
273
|
)
|
261
274
|
end
|
262
275
|
|
276
|
+
# Reorders intermediate items to support moving an item from old_position to new_position.
|
277
|
+
def shuffle_positions_on_intermediate_items(old_position, new_position)
|
278
|
+
return if old_position == new_position
|
279
|
+
|
280
|
+
if old_position < new_position
|
281
|
+
# Decrement position of intermediate items
|
282
|
+
#
|
283
|
+
# e.g., if moving an item from 2 to 5,
|
284
|
+
# move [3, 4, 5] to [2, 3, 4]
|
285
|
+
acts_as_list_class.update_all(
|
286
|
+
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{old_position} AND #{position_column} <= #{new_position}"
|
287
|
+
)
|
288
|
+
else
|
289
|
+
# Increment position of intermediate items
|
290
|
+
#
|
291
|
+
# e.g., if moving an item from 5 to 2,
|
292
|
+
# move [2, 3, 4] to [3, 4, 5]
|
293
|
+
acts_as_list_class.update_all(
|
294
|
+
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{new_position} AND #{position_column} < #{old_position}"
|
295
|
+
)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
263
299
|
def insert_at_position(position)
|
264
|
-
|
265
|
-
|
300
|
+
if in_list?
|
301
|
+
old_position = send(position_column).to_i
|
302
|
+
return if position == old_position
|
303
|
+
shuffle_positions_on_intermediate_items(old_position, position)
|
304
|
+
else
|
305
|
+
increment_positions_on_lower_items(position)
|
306
|
+
end
|
266
307
|
self.update_attribute(position_column, position)
|
267
308
|
end
|
309
|
+
|
310
|
+
# used by insert_at_position instead of remove_from_list, as postgresql raises error if position_column has non-null constraint
|
311
|
+
def store_at_0
|
312
|
+
if in_list?
|
313
|
+
old_position = send(position_column).to_i
|
314
|
+
update_attribute(position_column, 0)
|
315
|
+
decrement_positions_on_lower_items(old_position)
|
316
|
+
end
|
317
|
+
end
|
268
318
|
end
|
269
319
|
end
|
270
320
|
end
|
data/lib/acts_as_list/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/shared.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
module Shared
|
2
|
+
module ArrayScopeList
|
3
|
+
def setup
|
4
|
+
(1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter, :parent_id => 5, :parent_type => 'ParentClass' }
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_reordering
|
8
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
9
|
+
|
10
|
+
ArrayScopeListMixin.find(2).move_lower
|
11
|
+
assert_equal [1, 3, 2, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
12
|
+
|
13
|
+
ArrayScopeListMixin.find(2).move_higher
|
14
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
15
|
+
|
16
|
+
ArrayScopeListMixin.find(1).move_to_bottom
|
17
|
+
assert_equal [2, 3, 4, 1], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
18
|
+
|
19
|
+
ArrayScopeListMixin.find(1).move_to_top
|
20
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
21
|
+
|
22
|
+
ArrayScopeListMixin.find(2).move_to_bottom
|
23
|
+
assert_equal [1, 3, 4, 2], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
24
|
+
|
25
|
+
ArrayScopeListMixin.find(4).move_to_top
|
26
|
+
assert_equal [4, 1, 3, 2], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_move_to_bottom_with_next_to_last_item
|
30
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
31
|
+
ArrayScopeListMixin.find(3).move_to_bottom
|
32
|
+
assert_equal [1, 2, 4, 3], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_next_prev
|
36
|
+
assert_equal ArrayScopeListMixin.find(2), ArrayScopeListMixin.find(1).lower_item
|
37
|
+
assert_nil ArrayScopeListMixin.find(1).higher_item
|
38
|
+
assert_equal ArrayScopeListMixin.find(3), ArrayScopeListMixin.find(4).higher_item
|
39
|
+
assert_nil ArrayScopeListMixin.find(4).lower_item
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_injection
|
43
|
+
item = ArrayScopeListMixin.new(:parent_id => 1, :parent_type => 'ParentClass')
|
44
|
+
assert_equal '"mixins"."parent_id" = 1 AND "mixins"."parent_type" = \'ParentClass\'', item.scope_condition
|
45
|
+
assert_equal "pos", item.position_column
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_insert
|
49
|
+
new = ArrayScopeListMixin.create(:parent_id => 20, :parent_type => 'ParentClass')
|
50
|
+
assert_equal 1, new.pos
|
51
|
+
assert new.first?
|
52
|
+
assert new.last?
|
53
|
+
|
54
|
+
new = ArrayScopeListMixin.create(:parent_id => 20, :parent_type => 'ParentClass')
|
55
|
+
assert_equal 2, new.pos
|
56
|
+
assert !new.first?
|
57
|
+
assert new.last?
|
58
|
+
|
59
|
+
new = ArrayScopeListMixin.create(:parent_id => 20, :parent_type => 'ParentClass')
|
60
|
+
assert_equal 3, new.pos
|
61
|
+
assert !new.first?
|
62
|
+
assert new.last?
|
63
|
+
|
64
|
+
new = ArrayScopeListMixin.create(:parent_id => 0, :parent_type => 'ParentClass')
|
65
|
+
assert_equal 1, new.pos
|
66
|
+
assert new.first?
|
67
|
+
assert new.last?
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_insert_at
|
71
|
+
new = ArrayScopeListMixin.create(:parent_id => 20, :parent_type => 'ParentClass')
|
72
|
+
assert_equal 1, new.pos
|
73
|
+
|
74
|
+
new = ArrayScopeListMixin.create(:parent_id => 20, :parent_type => 'ParentClass')
|
75
|
+
assert_equal 2, new.pos
|
76
|
+
|
77
|
+
new = ArrayScopeListMixin.create(:parent_id => 20, :parent_type => 'ParentClass')
|
78
|
+
assert_equal 3, new.pos
|
79
|
+
|
80
|
+
new4 = ArrayScopeListMixin.create(:parent_id => 20, :parent_type => 'ParentClass')
|
81
|
+
assert_equal 4, new4.pos
|
82
|
+
|
83
|
+
new4.insert_at(3)
|
84
|
+
assert_equal 3, new4.pos
|
85
|
+
|
86
|
+
new.reload
|
87
|
+
assert_equal 4, new.pos
|
88
|
+
|
89
|
+
new.insert_at(2)
|
90
|
+
assert_equal 2, new.pos
|
91
|
+
|
92
|
+
new4.reload
|
93
|
+
assert_equal 4, new4.pos
|
94
|
+
|
95
|
+
new5 = ArrayScopeListMixin.create(:parent_id => 20, :parent_type => 'ParentClass')
|
96
|
+
assert_equal 5, new5.pos
|
97
|
+
|
98
|
+
new5.insert_at(1)
|
99
|
+
assert_equal 1, new5.pos
|
100
|
+
|
101
|
+
new4.reload
|
102
|
+
assert_equal 5, new4.pos
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_delete_middle
|
106
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
107
|
+
|
108
|
+
ArrayScopeListMixin.find(2).destroy
|
109
|
+
|
110
|
+
assert_equal [1, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
111
|
+
|
112
|
+
assert_equal 1, ArrayScopeListMixin.find(1).pos
|
113
|
+
assert_equal 2, ArrayScopeListMixin.find(3).pos
|
114
|
+
assert_equal 3, ArrayScopeListMixin.find(4).pos
|
115
|
+
|
116
|
+
ArrayScopeListMixin.find(1).destroy
|
117
|
+
|
118
|
+
assert_equal [3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
119
|
+
|
120
|
+
assert_equal 1, ArrayScopeListMixin.find(3).pos
|
121
|
+
assert_equal 2, ArrayScopeListMixin.find(4).pos
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_remove_from_list_should_then_fail_in_list?
|
125
|
+
assert_equal true, ArrayScopeListMixin.find(1).in_list?
|
126
|
+
ArrayScopeListMixin.find(1).remove_from_list
|
127
|
+
assert_equal false, ArrayScopeListMixin.find(1).in_list?
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_remove_from_list_should_set_position_to_nil
|
131
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
132
|
+
|
133
|
+
ArrayScopeListMixin.find(2).remove_from_list
|
134
|
+
|
135
|
+
assert_equal [2, 1, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
136
|
+
|
137
|
+
assert_equal 1, ArrayScopeListMixin.find(1).pos
|
138
|
+
assert_equal nil, ArrayScopeListMixin.find(2).pos
|
139
|
+
assert_equal 2, ArrayScopeListMixin.find(3).pos
|
140
|
+
assert_equal 3, ArrayScopeListMixin.find(4).pos
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_remove_before_destroy_does_not_shift_lower_items_twice
|
144
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
145
|
+
|
146
|
+
ArrayScopeListMixin.find(2).remove_from_list
|
147
|
+
ArrayScopeListMixin.find(2).destroy
|
148
|
+
|
149
|
+
assert_equal [1, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
150
|
+
|
151
|
+
assert_equal 1, ArrayScopeListMixin.find(1).pos
|
152
|
+
assert_equal 2, ArrayScopeListMixin.find(3).pos
|
153
|
+
assert_equal 3, ArrayScopeListMixin.find(4).pos
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/test/shared_list.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
module Shared
|
2
|
+
module List
|
3
|
+
def setup
|
4
|
+
(1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_reordering
|
8
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
9
|
+
|
10
|
+
ListMixin.find(2).move_lower
|
11
|
+
assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
12
|
+
|
13
|
+
ListMixin.find(2).move_higher
|
14
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
15
|
+
|
16
|
+
ListMixin.find(1).move_to_bottom
|
17
|
+
assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
18
|
+
|
19
|
+
ListMixin.find(1).move_to_top
|
20
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
21
|
+
|
22
|
+
ListMixin.find(2).move_to_bottom
|
23
|
+
assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
24
|
+
|
25
|
+
ListMixin.find(4).move_to_top
|
26
|
+
assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_move_to_bottom_with_next_to_last_item
|
30
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
31
|
+
ListMixin.find(3).move_to_bottom
|
32
|
+
assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_next_prev
|
36
|
+
assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
|
37
|
+
assert_nil ListMixin.find(1).higher_item
|
38
|
+
assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
|
39
|
+
assert_nil ListMixin.find(4).lower_item
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_injection
|
43
|
+
item = ListMixin.new(:parent_id => 1)
|
44
|
+
assert_equal '"mixins"."parent_id" = 1', item.scope_condition
|
45
|
+
assert_equal "pos", item.position_column
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_insert
|
49
|
+
new = ListMixin.create(:parent_id => 20)
|
50
|
+
assert_equal 1, new.pos
|
51
|
+
assert new.first?
|
52
|
+
assert new.last?
|
53
|
+
|
54
|
+
new = ListMixin.create(:parent_id => 20)
|
55
|
+
assert_equal 2, new.pos
|
56
|
+
assert !new.first?
|
57
|
+
assert new.last?
|
58
|
+
|
59
|
+
new = ListMixin.create(:parent_id => 20)
|
60
|
+
assert_equal 3, new.pos
|
61
|
+
assert !new.first?
|
62
|
+
assert new.last?
|
63
|
+
|
64
|
+
new = ListMixin.create(:parent_id => 0)
|
65
|
+
assert_equal 1, new.pos
|
66
|
+
assert new.first?
|
67
|
+
assert new.last?
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_insert_at
|
71
|
+
new = ListMixin.create(:parent_id => 20)
|
72
|
+
assert_equal 1, new.pos
|
73
|
+
|
74
|
+
new = ListMixin.create(:parent_id => 20)
|
75
|
+
assert_equal 2, new.pos
|
76
|
+
|
77
|
+
new = ListMixin.create(:parent_id => 20)
|
78
|
+
assert_equal 3, new.pos
|
79
|
+
|
80
|
+
new4 = ListMixin.create(:parent_id => 20)
|
81
|
+
assert_equal 4, new4.pos
|
82
|
+
|
83
|
+
new4.insert_at(3)
|
84
|
+
assert_equal 3, new4.pos
|
85
|
+
|
86
|
+
new.reload
|
87
|
+
assert_equal 4, new.pos
|
88
|
+
|
89
|
+
new.insert_at(2)
|
90
|
+
assert_equal 2, new.pos
|
91
|
+
|
92
|
+
new4.reload
|
93
|
+
assert_equal 4, new4.pos
|
94
|
+
|
95
|
+
new5 = ListMixin.create(:parent_id => 20)
|
96
|
+
assert_equal 5, new5.pos
|
97
|
+
|
98
|
+
new5.insert_at(1)
|
99
|
+
assert_equal 1, new5.pos
|
100
|
+
|
101
|
+
new4.reload
|
102
|
+
assert_equal 5, new4.pos
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_insert_middle_with_unsaved_item
|
106
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
107
|
+
|
108
|
+
# new item, not saved yet
|
109
|
+
insert_this = ListMixin.new(:parent_id => 5)
|
110
|
+
insert_this.insert_at(2)
|
111
|
+
|
112
|
+
assert_equal [1, 2, 3, 4, 5], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:pos)
|
113
|
+
assert_equal [1, 5, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_delete_middle
|
117
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
118
|
+
|
119
|
+
ListMixin.find(2).destroy
|
120
|
+
|
121
|
+
assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
122
|
+
|
123
|
+
assert_equal 1, ListMixin.find(1).pos
|
124
|
+
assert_equal 2, ListMixin.find(3).pos
|
125
|
+
assert_equal 3, ListMixin.find(4).pos
|
126
|
+
|
127
|
+
ListMixin.find(1).destroy
|
128
|
+
|
129
|
+
assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
130
|
+
|
131
|
+
assert_equal 1, ListMixin.find(3).pos
|
132
|
+
assert_equal 2, ListMixin.find(4).pos
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_with_string_based_scope
|
136
|
+
new = ListWithStringScopeMixin.create(:parent_id => 500)
|
137
|
+
assert_equal 1, new.pos
|
138
|
+
assert new.first?
|
139
|
+
assert new.last?
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_nil_scope
|
143
|
+
new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
|
144
|
+
new2.move_higher
|
145
|
+
assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_remove_from_list_should_then_fail_in_list?
|
149
|
+
assert_equal true, ListMixin.find(1).in_list?
|
150
|
+
ListMixin.find(1).remove_from_list
|
151
|
+
assert_equal false, ListMixin.find(1).in_list?
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_remove_from_list_should_set_position_to_nil
|
155
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
156
|
+
|
157
|
+
ListMixin.find(2).remove_from_list
|
158
|
+
|
159
|
+
assert_equal [2, 1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
160
|
+
|
161
|
+
assert_equal 1, ListMixin.find(1).pos
|
162
|
+
assert_equal nil, ListMixin.find(2).pos
|
163
|
+
assert_equal 2, ListMixin.find(3).pos
|
164
|
+
assert_equal 3, ListMixin.find(4).pos
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_remove_before_destroy_does_not_shift_lower_items_twice
|
168
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
169
|
+
|
170
|
+
ListMixin.find(2).remove_from_list
|
171
|
+
ListMixin.find(2).destroy
|
172
|
+
|
173
|
+
assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
174
|
+
|
175
|
+
assert_equal 1, ListMixin.find(1).pos
|
176
|
+
assert_equal 2, ListMixin.find(3).pos
|
177
|
+
assert_equal 3, ListMixin.find(4).pos
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_before_destroy_callbacks_do_not_update_position_to_nil_before_deleting_the_record
|
181
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
182
|
+
|
183
|
+
# We need to trigger all the before_destroy callbacks without actually
|
184
|
+
# destroying the record so we can see the affect the callbacks have on
|
185
|
+
# the record.
|
186
|
+
# NOTE: Hotfix for rails3 ActiveRecord
|
187
|
+
list = ListMixin.find(2)
|
188
|
+
if list.respond_to?(:run_callbacks)
|
189
|
+
# Refactored to work according to Rails3 ActiveRSupport Callbacks <http://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html>
|
190
|
+
list.run_callbacks :destroy, :before if rails_3
|
191
|
+
list.run_callbacks(:before_destroy) if !rails_3
|
192
|
+
else
|
193
|
+
list.send(:callback, :before_destroy)
|
194
|
+
end
|
195
|
+
|
196
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
197
|
+
|
198
|
+
assert_equal 1, ListMixin.find(1).pos
|
199
|
+
assert_equal 2, ListMixin.find(2).pos
|
200
|
+
assert_equal 2, ListMixin.find(3).pos
|
201
|
+
assert_equal 3, ListMixin.find(4).pos
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_before_create_callback_adds_to_bottom
|
205
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
206
|
+
|
207
|
+
new = ListMixin.create(:parent_id => 5)
|
208
|
+
assert_equal 5, new.pos
|
209
|
+
assert !new.first?
|
210
|
+
assert new.last?
|
211
|
+
|
212
|
+
assert_equal [1, 2, 3, 4, 5], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_before_create_callback_adds_to_given_position
|
216
|
+
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
217
|
+
|
218
|
+
new = ListMixin.create(:pos => 1, :parent_id => 5)
|
219
|
+
assert_equal 1, new.pos
|
220
|
+
assert new.first?
|
221
|
+
assert !new.last?
|
222
|
+
|
223
|
+
assert_equal [5, 1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
224
|
+
|
225
|
+
new = ListMixin.create(:pos => 3, :parent_id => 5)
|
226
|
+
assert_equal 3, new.pos
|
227
|
+
assert !new.first?
|
228
|
+
assert !new.last?
|
229
|
+
|
230
|
+
assert_equal [5, 1, 6, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|