acts_as_list_ar 0.1.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,79 @@
1
+ module ActsAsListAR
2
+ def scope_name
3
+ "named_scope"
4
+ end
5
+
6
+ module ClassMethods
7
+ # nothing here, folks
8
+ end
9
+
10
+ module InstanceMethods
11
+ # Return the next higher item in the list.
12
+ def higher_item
13
+ return nil unless in_list?
14
+ acts_as_list_class.find(:first, :conditions =>
15
+ "#{scope_condition} AND #{position_column} < #{send(position_column).to_s}",
16
+ :order => "#{position_column} DESC"
17
+ )
18
+ end
19
+
20
+ # Return the next lower item in the list.
21
+ def lower_item
22
+ return nil unless in_list?
23
+ acts_as_list_class.find(:first, :conditions =>
24
+ "#{scope_condition} AND #{position_column} > #{send(position_column).to_s}",
25
+ :order => "#{position_column} ASC"
26
+ )
27
+ end
28
+
29
+ private
30
+ # Returns the bottom item
31
+ def bottom_item(except = nil)
32
+ conditions = scope_condition
33
+ conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
34
+ acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
35
+ end
36
+
37
+ # This has the effect of moving all the higher items up one.
38
+ def decrement_positions_on_higher_items(position)
39
+ acts_as_list_class.update_all(
40
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
41
+ )
42
+ end
43
+
44
+ # This has the effect of moving all the lower items up one.
45
+ def decrement_positions_on_lower_items
46
+ return unless in_list?
47
+ acts_as_list_class.update_all(
48
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
49
+ )
50
+ end
51
+
52
+ # This has the effect of moving all the higher items down one.
53
+ def increment_positions_on_higher_items
54
+ return unless in_list?
55
+ acts_as_list_class.update_all(
56
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
57
+ )
58
+ end
59
+
60
+ # This has the effect of moving all the lower items down one.
61
+ def increment_positions_on_lower_items(position)
62
+ acts_as_list_class.update_all(
63
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
64
+ )
65
+ end
66
+
67
+ # Increments position (<tt>position_column</tt>) of all items in the list.
68
+ def increment_positions_on_all_items
69
+ acts_as_list_class.update_all(
70
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
71
+ )
72
+ end
73
+ end
74
+
75
+ def self.included(receiver)
76
+ receiver.extend ClassMethods
77
+ receiver.send :include, InstanceMethods
78
+ end
79
+ end
@@ -0,0 +1,282 @@
1
+ module ActsAsListAR
2
+ def scope_name
3
+ "scope"
4
+ end
5
+
6
+ module ClassMethods
7
+ # nothing here, folks
8
+ end
9
+
10
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
11
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
12
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
13
+ # the first in the list of all chapters.
14
+ module InstanceMethods
15
+ # Insert the item at the given position (defaults to the top position of 1).
16
+ def insert_at(position = 1)
17
+ insert_at_position(position)
18
+ end
19
+
20
+ # Inserts the item at the bottom of the list
21
+ def insert_at_bottom
22
+ assume_bottom_position
23
+ end
24
+
25
+ def insert_into(list, options = {})
26
+ position = options.delete(:position) || list.size
27
+ options.each { |attr, value| self.send "#{attr}=", value }
28
+
29
+ list.insert(position, self)
30
+ list.each_with_index { |item, index| item.update_attribute(:position, index + 1) }
31
+ end
32
+
33
+ def move_to(position = 1)
34
+ move_to_position(position)
35
+ end
36
+
37
+ def remove_from(list, options = {})
38
+ if in_list?
39
+ decrement_positions_on_lower_items
40
+
41
+ self.send "#{position_column}=", nil
42
+ options.each { |attr, value| self.send "#{attr}=", value }
43
+ list.delete self
44
+ end
45
+ end
46
+
47
+ # Swap positions with the next lower item, if one exists.
48
+ def move_lower
49
+ lower = lower_item
50
+ return unless lower
51
+ acts_as_list_class.transaction do
52
+ self.update_attribute(position_column, lower.send(position_column))
53
+ lower.decrement_position
54
+ end
55
+ end
56
+
57
+ # Swap positions with the next higher item, if one exists.
58
+ def move_higher
59
+ higher = higher_item
60
+ return unless higher
61
+ acts_as_list_class.transaction do
62
+ self.update_attribute(position_column, higher.send(position_column))
63
+ higher.increment_position
64
+ end
65
+ end
66
+
67
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
68
+ # position adjusted accordingly.
69
+ def move_to_bottom
70
+ # return unless in_list?
71
+ acts_as_list_class.transaction do
72
+ decrement_positions_on_lower_items if in_list?
73
+ assume_bottom_position
74
+ end
75
+ end
76
+
77
+ # Move to the top of the list. If the item is already in the list, the items above it have their
78
+ # position adjusted accordingly.
79
+ def move_to_top
80
+ # return unless in_list?
81
+ acts_as_list_class.transaction do
82
+ # increment_positions_on_higher_items
83
+ in_list? ? increment_positions_on_higher_items : increment_positions_on_all_items
84
+ assume_top_position
85
+ end
86
+ end
87
+
88
+ # Removes the item from the list.
89
+ def remove_from_list
90
+ # if in_list?
91
+ # decrement_positions_on_lower_items
92
+ # update_attribute position_column, nil
93
+ # end
94
+ return unless in_list?
95
+ decrement_positions_on_lower_items
96
+ update_attribute position_column, nil
97
+ end
98
+
99
+ # Increase the position of this item without adjusting the rest of the list.
100
+ def increment_position
101
+ # return unless in_list?
102
+ update_attribute position_column, self.send(position_column).to_i + 1
103
+ end
104
+
105
+ # Decrease the position of this item without adjusting the rest of the list.
106
+ def decrement_position
107
+ # return unless in_list?
108
+ update_attribute position_column, self.send(position_column).to_i - 1
109
+ end
110
+
111
+ # Return +true+ if this object is the first in the list.
112
+ def first?
113
+ # return false unless in_list?
114
+ self.send(position_column) == 1
115
+ end
116
+
117
+ # Return +true+ if this object is the last in the list.
118
+ def last?
119
+ # return false unless in_list?
120
+ self.send(position_column) == bottom_position_in_list
121
+ end
122
+
123
+ # Return the next higher item in the list.
124
+ def higher_item
125
+ # return nil unless in_list? # http://github.com/brightspark3/acts_as_list/commit/8e55352aaa437d23a1ebdeabd5276c6dd5aad6a1
126
+
127
+ # acts_as_list_class.find(:first, :conditions =>
128
+ # "#{scope_condition} AND #{position_column} < #{send(position_column).to_s}", :order => "#{position_column} DESC"
129
+ # )
130
+ acts_as_list_class.with_acts_as_list_scope(scope_condition) do
131
+ where("#{position_column} = #{(send(position_column).to_i - 1).to_s}").first
132
+ end
133
+ end
134
+
135
+ # Return the next lower item in the list.
136
+ def lower_item
137
+ # return nil unless in_list?
138
+ # acts_as_list_class.find(:first, :conditions => "#{scope_condition} AND #{position_column} > #{send(position_column).to_s}", :order => "#{position_column} ASC")
139
+ acts_as_list_class.with_acts_as_list_scope(scope_condition) do
140
+ where("#{position_column} = #{(send(position_column).to_i + 1).to_s}").first
141
+ end
142
+ end
143
+
144
+ # Test if this record is in a list
145
+ def in_list?
146
+ !send(position_column).nil?
147
+ end
148
+
149
+ private
150
+ def add_to_list_top
151
+ increment_positions_on_all_items
152
+ end
153
+
154
+ def add_to_list_bottom_when_necessary
155
+ self[position_column] = bottom_position_in_list.to_i + 1 if send(position_column).nil?
156
+ end
157
+
158
+ def add_to_list_bottom
159
+ self[position_column] = bottom_position_in_list.to_i + 1
160
+ end
161
+
162
+ # Overwrite this method to define the scope of the list changes
163
+ def scope_condition() "1" end
164
+
165
+ # Returns the bottom position number in the list.
166
+ # bottom_position_in_list # => 2
167
+ def bottom_position_in_list(except = nil)
168
+ item = bottom_item(except)
169
+ item ? item.send(position_column) : 0
170
+ end
171
+
172
+ # Returns the bottom item
173
+ def bottom_item(except = nil)
174
+ # conditions = scope_condition
175
+ # conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
176
+ # acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
177
+ acts_as_list_class.with_acts_as_list_scope(scope_condition) do
178
+ conditions = except.nil?() ? "" : "#{self.class.primary_key} != #{except.id}"
179
+ where(conditions).order("#{position_column} DESC").first
180
+ end
181
+ end
182
+
183
+ # Forces item to assume the bottom position in the list.
184
+ def assume_bottom_position
185
+ update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
186
+ end
187
+
188
+ # Forces item to assume the top position in the list.
189
+ def assume_top_position
190
+ update_attribute(position_column, 1)
191
+ end
192
+
193
+ # This has the effect of moving all the higher items up one.
194
+ def decrement_positions_on_higher_items(position)
195
+ # acts_as_list_class.update_all("#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}")
196
+ acts_as_list_class.with_acts_as_list_scope(scope_condition) do
197
+ update_all("#{position_column} = (#{position_column} - 1)", "#{position_column} <= #{position}")
198
+ end
199
+ end
200
+
201
+ # This has the effect of moving all the lower items up one.
202
+ def decrement_positions_on_lower_items
203
+ # return unless in_list?
204
+ # acts_as_list_class.update_all("#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}")
205
+ acts_as_list_class.with_acts_as_list_scope(scope_condition) do
206
+ update_all("#{position_column} = (#{position_column} - 1)", "#{position_column} > #{send(position_column).to_i}")
207
+ end
208
+ end
209
+
210
+ # This has the effect of moving all the higher items down one.
211
+ def increment_positions_on_higher_items
212
+ # return unless in_list?
213
+ # acts_as_list_class.update_all("#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}")
214
+ acts_as_list_class.with_acts_as_list_scope(scope_condition) do
215
+ update_all("#{position_column} = (#{position_column} + 1)", "#{position_column} < #{send(position_column).to_i}")
216
+ end
217
+ end
218
+
219
+ # This has the effect of moving all the lower items down one.
220
+ def increment_positions_on_lower_items(position)
221
+ # acts_as_list_class.update_all("#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}")
222
+ acts_as_list_class.with_acts_as_list_scope(scope_condition) do
223
+ update_all("#{position_column} = (#{position_column} + 1)", "#{position_column} >= #{position}")
224
+ end
225
+ end
226
+
227
+ # Increments position (<tt>position_column</tt>) of all items in the list.
228
+ def increment_positions_on_all_items
229
+ # acts_as_list_class.update_all("#{position_column} = (#{position_column} + 1)", "#{scope_condition}")
230
+ acts_as_list_class.with_acts_as_list_scope(scope_condition) do
231
+ update_all("#{position_column} = (#{position_column} + 1)")
232
+ end
233
+ end
234
+
235
+ def insert_at_position(position)
236
+ remove_from_list
237
+ increment_positions_on_lower_items(position)
238
+ self.update_attribute(position_column, position)
239
+ end
240
+
241
+
242
+ # This has the effect of moving all items between two positions (inclusive) up one.
243
+ def decrement_positions_between(low, high)
244
+ acts_as_list_class.update_all(
245
+ "#{position_column} = (#{position_column} - 1)", ["#{scope_condition} AND #{position_column} >= ? AND #{position_column} <= ?", low, high]
246
+ )
247
+ end
248
+
249
+ # This has the effect of moving all items between two positions (inclusive) down one.
250
+ def increment_positions_between(low, high)
251
+ acts_as_list_class.update_all(
252
+ "#{position_column} = (#{position_column} + 1)", ["#{scope_condition} AND #{position_column} >= ? AND #{position_column} <= ?", low, high]
253
+ )
254
+ end
255
+
256
+ # Moves an existing list element to the "new_position" slot.
257
+ def move_to_position(new_position)
258
+ old_position = self.send(position_column)
259
+ unless new_position == old_position
260
+ if new_position < old_position
261
+ # Moving higher in the list (up)
262
+ new_position = [1, new_position].max
263
+ increment_positions_between(new_position, old_position - 1)
264
+ else
265
+ # Moving lower in the list (down)
266
+ new_position = [bottom_position_in_list(self).to_i, new_position].min
267
+ decrement_positions_between(old_position + 1, new_position)
268
+ end
269
+ self.update_attribute(position_column, new_position)
270
+ end
271
+ end
272
+
273
+ def eliminate_current_position
274
+ decrement_positions_on_lower_items if in_list?
275
+ end
276
+ end
277
+
278
+ def self.included(receiver)
279
+ receiver.extend ClassMethods
280
+ receiver.send :include, InstanceMethods
281
+ end
282
+ end
@@ -0,0 +1,112 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class ListTest
4
+ class << self
5
+ def before_destroy(*attr); end
6
+ def before_create(*attr); end
7
+ end
8
+
9
+ include ActiveRecord::Acts::List
10
+ acts_as_list :scope => :parent
11
+
12
+ attr_accessor :position, :attr1, :attr2
13
+ end
14
+
15
+ describe ActiveRecord::Acts::List do
16
+
17
+ before do
18
+ @elem = ListTest.new
19
+ @list = []
20
+ end
21
+
22
+ describe "insert at bottom" do
23
+ it "should insert the element at the bottom of the list" do
24
+ @elem.should_receive :assume_bottom_position
25
+ @elem.insert_at_bottom
26
+ end
27
+ end
28
+
29
+ describe "insert into" do
30
+ before do
31
+ @elem.stub! :update_attribute
32
+ end
33
+
34
+ it "should insert the element at the bottom" do
35
+ @list.should_receive(:insert).with(0, @elem)
36
+ @elem.insert_into @list
37
+ end
38
+
39
+ it "should set all the optional attributes to the given value" do
40
+ @elem.insert_into @list, :attr1 => "value1", :attr2 => "value2"
41
+ @elem.attr1.should == "value1"
42
+ @elem.attr2.should == "value2"
43
+ end
44
+
45
+ it "should insert into a specified position" do
46
+ @list.should_receive(:insert).with(41, @elem)
47
+ @elem.insert_into @list, :position => 41
48
+ end
49
+
50
+ it "should update the position on all attributes in the list" do
51
+ @elem2 = ListTest.new
52
+ @list = [@elem2]
53
+
54
+ @elem.should_receive(:update_attribute).with(:position, 2)
55
+ @elem2.should_receive(:update_attribute).with(:position, 1)
56
+
57
+ @elem.insert_into @list, :position => 1
58
+ end
59
+ end
60
+
61
+ describe "remove from" do
62
+ it "should not do anything if the elem is not in the list" do
63
+ @elem.should_not_receive :decrement_positions_on_lower_items
64
+ @elem.remove_from @list
65
+ end
66
+
67
+ describe "in list" do
68
+ before do
69
+ @list = [@elem]
70
+ @elem.position = 1
71
+ @elem.stub! :decrement_positions_on_lower_items
72
+ end
73
+
74
+ it "should decrement the position on all lower items" do
75
+ @elem.should_receive :decrement_positions_on_lower_items
76
+ @elem.remove_from @list
77
+ end
78
+
79
+ it "should set the position to nil" do
80
+ @elem.remove_from @list
81
+ @elem.position.should be_nil
82
+ end
83
+
84
+ it "should delete the element from the list" do
85
+ @elem.remove_from @list
86
+ @list.should_not include(@elem)
87
+ end
88
+
89
+ it "should set all the optional attributes to the given value" do
90
+ @elem.remove_from @list, :attr1 => "unset1", :attr2 => "unset2"
91
+ @elem.attr1.should == "unset1"
92
+ @elem.attr2.should == "unset2"
93
+ end
94
+ end
95
+ end
96
+
97
+ { "higher_items" => :<, "lower_items" => :> }.each do |items, operator|
98
+ describe items do
99
+ it "should return nil if the element is not in the list" do
100
+ @elem.send(items).should be_nil
101
+ end
102
+
103
+ it "should find all #{items} with a ActiveRecord finder" do
104
+ @elem.position = 23
105
+ @elem.stub!(:scope_condition).and_return "scope"
106
+ ListTest.should_receive(:find).with :all, :conditions => "scope AND position #{operator} 23"
107
+
108
+ @elem.send items
109
+ end
110
+ end
111
+ end
112
+ end