acts_as_list 0.1.6 → 0.2.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.
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/README.md +63 -9
- data/acts_as_list.gemspec +1 -1
- data/lib/acts_as_list/active_record/acts/list.rb +126 -35
- data/lib/acts_as_list/version.rb +1 -1
- data/lib/acts_as_list.rb +2 -0
- data/test/shared_array_scope_list.rb +5 -1
- data/test/shared_list.rb +11 -3
- data/test/shared_list_sub.rb +21 -1
- data/test/test_list.rb +220 -1
- metadata +88 -57
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
|
@@ -4,24 +4,78 @@
|
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
In your Gemfile:
|
|
10
|
+
|
|
11
|
+
gem 'acts_as_list'
|
|
12
|
+
|
|
13
|
+
Or, from the command line:
|
|
14
|
+
|
|
15
|
+
gem install acts_as_list
|
|
7
16
|
|
|
8
17
|
## Example
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
At first, you need to add a `position` column to desired table:
|
|
20
|
+
|
|
21
|
+
rails g migration AddPositionToTodoItem position:integer
|
|
22
|
+
rake db:migrate
|
|
13
23
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
After that you can use `acts_as_list` method in the model:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class TodoList < ActiveRecord::Base
|
|
28
|
+
has_many :todo_items, order: :position
|
|
29
|
+
end
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
class TodoItem < ActiveRecord::Base
|
|
32
|
+
belongs_to :todo_list
|
|
33
|
+
acts_as_list scope: :todo_list
|
|
34
|
+
end
|
|
21
35
|
|
|
36
|
+
todo_list.first.move_to_bottom
|
|
37
|
+
todo_list.last.move_higher
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Instance Methods Added To ActiveRecord Models
|
|
41
|
+
|
|
42
|
+
You'll have a number of methods added to each instance of the ActiveRecord model that to which `acts_as_list` is added.
|
|
43
|
+
|
|
44
|
+
In `acts_as_list`, "higher" means further up the list (a lower `position`), and "lower" means further down the list (a higher `position`). That can be confusing, so it might make sense to add tests that validate that you're using the right method given your context.
|
|
45
|
+
|
|
46
|
+
### Methods That Change Position and Reorder List
|
|
47
|
+
|
|
48
|
+
- `list_item.insert_at(2)`
|
|
49
|
+
- `list_item.move_lower` will do nothing if the item is the lowest item
|
|
50
|
+
- `list_item.move_higher` will do nothing if the item is the highest item
|
|
51
|
+
- `list_item.move_to_bottom`
|
|
52
|
+
- `list_item.move_to_top`
|
|
53
|
+
- `list_item.remove_from_list`
|
|
54
|
+
|
|
55
|
+
### Methods That Change Position Without Reordering List
|
|
56
|
+
|
|
57
|
+
- `list_item.increment_position`
|
|
58
|
+
- `list_item.decrement_position`
|
|
59
|
+
- `list_item.set_list_position(3)`
|
|
60
|
+
|
|
61
|
+
### Methods That Return Attributes of the Item's List Position
|
|
62
|
+
- `list_item.first?`
|
|
63
|
+
- `list_item.last?`
|
|
64
|
+
- `list_item.in_list?`
|
|
65
|
+
- `list_item.not_in_list?`
|
|
66
|
+
- `list_item.default_position?`
|
|
67
|
+
- `list_item.higher_item`
|
|
68
|
+
- `list_item.higher_items` will return all the items above `list_item` in the list (ordered by the position, ascending)
|
|
69
|
+
- `list_item.lower_item`
|
|
70
|
+
- `list_item.lower_items` will return all the items below `list_item` in the list (ordered by the position, ascending)
|
|
71
|
+
|
|
22
72
|
## Notes
|
|
23
73
|
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
74
|
|
|
75
|
+
All `position` queries (select, update, etc.) inside gem methods are executed without the default scope (i.e. `Model.unscoped`), this will prevent nasty issues when the default scope is different from `acts_as_list` scope.
|
|
76
|
+
|
|
77
|
+
The `position` column is set after validations are called, so you should not put a `presence` validation on the `position` column.
|
|
78
|
+
|
|
25
79
|
## Versions
|
|
26
80
|
All versions `0.1.5` onwards require Rails 3.0.x and higher.
|
|
27
81
|
|
data/acts_as_list.gemspec
CHANGED
|
@@ -24,8 +24,8 @@ Gem::Specification.new do |s|
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
# Dependencies (installed via 'bundle install')...
|
|
27
|
+
s.add_dependency("activerecord", [">= 3.0"])
|
|
27
28
|
s.add_development_dependency("bundler", [">= 1.0.0"])
|
|
28
|
-
s.add_development_dependency("activerecord", [">= 1.15.4.7794"])
|
|
29
29
|
s.add_development_dependency("rdoc")
|
|
30
30
|
s.add_development_dependency("sqlite3")
|
|
31
31
|
end
|
|
@@ -26,8 +26,8 @@ module ActiveRecord
|
|
|
26
26
|
# Configuration options are:
|
|
27
27
|
#
|
|
28
28
|
# * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
|
|
29
|
-
# * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
|
|
30
|
-
# (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
|
|
29
|
+
# * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
|
|
30
|
+
# (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
|
|
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
|
|
@@ -48,7 +48,7 @@ module ActiveRecord
|
|
|
48
48
|
elsif configuration[:scope].is_a?(Array)
|
|
49
49
|
scope_condition_method = %(
|
|
50
50
|
def scope_condition
|
|
51
|
-
attrs = %w(#{configuration[:scope].join(" ")}).inject({}) do |memo,column|
|
|
51
|
+
attrs = %w(#{configuration[:scope].join(" ")}).inject({}) do |memo,column|
|
|
52
52
|
memo[column.intern] = send(column.intern); memo
|
|
53
53
|
end
|
|
54
54
|
self.class.send(:sanitize_sql_hash_for_conditions, attrs)
|
|
@@ -73,10 +73,28 @@ module ActiveRecord
|
|
|
73
73
|
'#{configuration[:column]}'
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
def scope_name
|
|
77
|
+
'#{configuration[:scope]}'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def add_new_at
|
|
81
|
+
'#{configuration[:add_new_at]}'
|
|
82
|
+
end
|
|
83
|
+
|
|
76
84
|
#{scope_condition_method}
|
|
77
85
|
|
|
86
|
+
# only add to attr_accessible
|
|
87
|
+
# if the class has some mass_assignment_protection
|
|
88
|
+
|
|
89
|
+
if defined?(accessible_attributes) and !accessible_attributes.blank?
|
|
90
|
+
attr_accessible :#{configuration[:column]}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
before_destroy :reload_position
|
|
78
94
|
after_destroy :decrement_positions_on_lower_items
|
|
79
95
|
before_create :add_to_list_#{configuration[:add_new_at]}
|
|
96
|
+
after_update :update_positions
|
|
97
|
+
before_update :check_scope
|
|
80
98
|
EOV
|
|
81
99
|
end
|
|
82
100
|
end
|
|
@@ -135,20 +153,20 @@ module ActiveRecord
|
|
|
135
153
|
def remove_from_list
|
|
136
154
|
if in_list?
|
|
137
155
|
decrement_positions_on_lower_items
|
|
138
|
-
|
|
156
|
+
set_list_position(nil)
|
|
139
157
|
end
|
|
140
158
|
end
|
|
141
159
|
|
|
142
160
|
# Increase the position of this item without adjusting the rest of the list.
|
|
143
161
|
def increment_position
|
|
144
162
|
return unless in_list?
|
|
145
|
-
|
|
163
|
+
set_list_position(self.send(position_column).to_i + 1)
|
|
146
164
|
end
|
|
147
165
|
|
|
148
166
|
# Decrease the position of this item without adjusting the rest of the list.
|
|
149
167
|
def decrement_position
|
|
150
168
|
return unless in_list?
|
|
151
|
-
|
|
169
|
+
set_list_position(self.send(position_column).to_i - 1)
|
|
152
170
|
end
|
|
153
171
|
|
|
154
172
|
# Return +true+ if this object is the first in the list.
|
|
@@ -166,28 +184,54 @@ module ActiveRecord
|
|
|
166
184
|
# Return the next higher item in the list.
|
|
167
185
|
def higher_item
|
|
168
186
|
return nil unless in_list?
|
|
169
|
-
acts_as_list_class.find(:first, :conditions =>
|
|
170
|
-
"#{scope_condition} AND #{position_column}
|
|
187
|
+
acts_as_list_class.unscoped.find(:first, :conditions =>
|
|
188
|
+
"#{scope_condition} AND #{position_column} < #{(send(position_column).to_i).to_s}",
|
|
189
|
+
:order => "#{acts_as_list_class.table_name}.#{position_column} DESC"
|
|
171
190
|
)
|
|
172
191
|
end
|
|
173
192
|
|
|
193
|
+
# Return the next n higher items in the list
|
|
194
|
+
# selects all higher items by default
|
|
195
|
+
def higher_items(limit=nil)
|
|
196
|
+
limit ||= acts_as_list_list.count
|
|
197
|
+
position_value = send(position_column)
|
|
198
|
+
acts_as_list_list.
|
|
199
|
+
where("#{position_column} < ?", position_value).
|
|
200
|
+
where("#{position_column} >= ?", position_value - limit).
|
|
201
|
+
limit(limit).
|
|
202
|
+
order("#{acts_as_list_class.table_name}.#{position_column} ASC")
|
|
203
|
+
end
|
|
204
|
+
|
|
174
205
|
# Return the next lower item in the list.
|
|
175
206
|
def lower_item
|
|
176
207
|
return nil unless in_list?
|
|
177
|
-
acts_as_list_class.find(:first, :conditions =>
|
|
178
|
-
"#{scope_condition} AND #{position_column}
|
|
208
|
+
acts_as_list_class.unscoped.find(:first, :conditions =>
|
|
209
|
+
"#{scope_condition} AND #{position_column} > #{(send(position_column).to_i).to_s}",
|
|
210
|
+
:order => "#{acts_as_list_class.table_name}.#{position_column} ASC"
|
|
179
211
|
)
|
|
180
212
|
end
|
|
181
213
|
|
|
214
|
+
# Return the next n lower items in the list
|
|
215
|
+
# selects all lower items by default
|
|
216
|
+
def lower_items(limit=nil)
|
|
217
|
+
limit ||= acts_as_list_list.count
|
|
218
|
+
position_value = send(position_column)
|
|
219
|
+
acts_as_list_list.
|
|
220
|
+
where("#{position_column} > ?", position_value).
|
|
221
|
+
where("#{position_column} <= ?", position_value + limit).
|
|
222
|
+
limit(limit).
|
|
223
|
+
order("#{acts_as_list_class.table_name}.#{position_column} ASC")
|
|
224
|
+
end
|
|
225
|
+
|
|
182
226
|
# Test if this record is in a list
|
|
183
227
|
def in_list?
|
|
184
228
|
!not_in_list?
|
|
185
229
|
end
|
|
186
|
-
|
|
230
|
+
|
|
187
231
|
def not_in_list?
|
|
188
232
|
send(position_column).nil?
|
|
189
233
|
end
|
|
190
|
-
|
|
234
|
+
|
|
191
235
|
def default_position
|
|
192
236
|
acts_as_list_class.columns_hash[position_column.to_s].default
|
|
193
237
|
end
|
|
@@ -196,7 +240,18 @@ module ActiveRecord
|
|
|
196
240
|
default_position == send(position_column)
|
|
197
241
|
end
|
|
198
242
|
|
|
243
|
+
# Sets the new position and saves it
|
|
244
|
+
def set_list_position(new_position)
|
|
245
|
+
send("#{position_column}=", new_position)
|
|
246
|
+
save!
|
|
247
|
+
end
|
|
248
|
+
|
|
199
249
|
private
|
|
250
|
+
def acts_as_list_list
|
|
251
|
+
acts_as_list_class.unscoped.
|
|
252
|
+
where(scope_condition)
|
|
253
|
+
end
|
|
254
|
+
|
|
200
255
|
def add_to_list_top
|
|
201
256
|
increment_positions_on_all_items
|
|
202
257
|
self[position_column] = acts_as_list_top
|
|
@@ -224,23 +279,25 @@ module ActiveRecord
|
|
|
224
279
|
def bottom_item(except = nil)
|
|
225
280
|
conditions = scope_condition
|
|
226
281
|
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
|
|
227
|
-
acts_as_list_class.unscoped.
|
|
282
|
+
acts_as_list_class.unscoped.where(conditions).order("#{acts_as_list_class.table_name}.#{position_column} DESC").first
|
|
228
283
|
end
|
|
229
284
|
|
|
230
285
|
# Forces item to assume the bottom position in the list.
|
|
231
286
|
def assume_bottom_position
|
|
232
|
-
|
|
287
|
+
set_list_position(bottom_position_in_list(self).to_i + 1)
|
|
233
288
|
end
|
|
234
289
|
|
|
235
290
|
# Forces item to assume the top position in the list.
|
|
236
291
|
def assume_top_position
|
|
237
|
-
|
|
292
|
+
set_list_position(acts_as_list_top)
|
|
238
293
|
end
|
|
239
294
|
|
|
240
295
|
# This has the effect of moving all the higher items up one.
|
|
241
296
|
def decrement_positions_on_higher_items(position)
|
|
242
|
-
acts_as_list_class.
|
|
243
|
-
"#{
|
|
297
|
+
acts_as_list_class.unscoped.where(
|
|
298
|
+
"#{scope_condition} AND #{position_column} <= #{position}"
|
|
299
|
+
).update_all(
|
|
300
|
+
"#{position_column} = (#{position_column} - 1)"
|
|
244
301
|
)
|
|
245
302
|
end
|
|
246
303
|
|
|
@@ -248,52 +305,64 @@ module ActiveRecord
|
|
|
248
305
|
def decrement_positions_on_lower_items(position=nil)
|
|
249
306
|
return unless in_list?
|
|
250
307
|
position ||= send(position_column).to_i
|
|
251
|
-
acts_as_list_class.
|
|
252
|
-
"#{
|
|
308
|
+
acts_as_list_class.unscoped.where(
|
|
309
|
+
"#{scope_condition} AND #{position_column} > #{position}"
|
|
310
|
+
).update_all(
|
|
311
|
+
"#{position_column} = (#{position_column} - 1)"
|
|
253
312
|
)
|
|
254
313
|
end
|
|
255
314
|
|
|
256
315
|
# This has the effect of moving all the higher items down one.
|
|
257
316
|
def increment_positions_on_higher_items
|
|
258
317
|
return unless in_list?
|
|
259
|
-
acts_as_list_class.
|
|
260
|
-
"#{
|
|
318
|
+
acts_as_list_class.unscoped.where(
|
|
319
|
+
"#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
|
|
320
|
+
).update_all(
|
|
321
|
+
"#{position_column} = (#{position_column} + 1)"
|
|
261
322
|
)
|
|
262
323
|
end
|
|
263
324
|
|
|
264
325
|
# This has the effect of moving all the lower items down one.
|
|
265
326
|
def increment_positions_on_lower_items(position)
|
|
266
|
-
acts_as_list_class.
|
|
267
|
-
"#{
|
|
268
|
-
|
|
327
|
+
acts_as_list_class.unscoped.where(
|
|
328
|
+
"#{scope_condition} AND #{position_column} >= #{position}"
|
|
329
|
+
).update_all(
|
|
330
|
+
"#{position_column} = (#{position_column} + 1)"
|
|
331
|
+
)
|
|
269
332
|
end
|
|
270
333
|
|
|
271
334
|
# Increments position (<tt>position_column</tt>) of all items in the list.
|
|
272
335
|
def increment_positions_on_all_items
|
|
273
|
-
acts_as_list_class.
|
|
274
|
-
"#{
|
|
336
|
+
acts_as_list_class.unscoped.where(
|
|
337
|
+
"#{scope_condition}"
|
|
338
|
+
).update_all(
|
|
339
|
+
"#{position_column} = (#{position_column} + 1)"
|
|
275
340
|
)
|
|
276
341
|
end
|
|
277
342
|
|
|
278
343
|
# Reorders intermediate items to support moving an item from old_position to new_position.
|
|
279
|
-
def shuffle_positions_on_intermediate_items(old_position, new_position)
|
|
344
|
+
def shuffle_positions_on_intermediate_items(old_position, new_position, avoid_id = nil)
|
|
280
345
|
return if old_position == new_position
|
|
281
|
-
|
|
346
|
+
avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{avoid_id}" : ''
|
|
282
347
|
if old_position < new_position
|
|
283
348
|
# Decrement position of intermediate items
|
|
284
349
|
#
|
|
285
350
|
# e.g., if moving an item from 2 to 5,
|
|
286
351
|
# move [3, 4, 5] to [2, 3, 4]
|
|
287
|
-
acts_as_list_class.
|
|
288
|
-
"#{
|
|
352
|
+
acts_as_list_class.unscoped.where(
|
|
353
|
+
"#{scope_condition} AND #{position_column} > #{old_position} AND #{position_column} <= #{new_position}#{avoid_id_condition}"
|
|
354
|
+
).update_all(
|
|
355
|
+
"#{position_column} = (#{position_column} - 1)"
|
|
289
356
|
)
|
|
290
357
|
else
|
|
291
358
|
# Increment position of intermediate items
|
|
292
359
|
#
|
|
293
360
|
# e.g., if moving an item from 5 to 2,
|
|
294
361
|
# move [2, 3, 4] to [3, 4, 5]
|
|
295
|
-
acts_as_list_class.
|
|
296
|
-
"#{
|
|
362
|
+
acts_as_list_class.unscoped.where(
|
|
363
|
+
"#{scope_condition} AND #{position_column} >= #{new_position} AND #{position_column} < #{old_position}#{avoid_id_condition}"
|
|
364
|
+
).update_all(
|
|
365
|
+
"#{position_column} = (#{position_column} + 1)"
|
|
297
366
|
)
|
|
298
367
|
end
|
|
299
368
|
end
|
|
@@ -306,18 +375,40 @@ module ActiveRecord
|
|
|
306
375
|
else
|
|
307
376
|
increment_positions_on_lower_items(position)
|
|
308
377
|
end
|
|
309
|
-
|
|
378
|
+
set_list_position(position)
|
|
310
379
|
end
|
|
311
380
|
|
|
312
381
|
# used by insert_at_position instead of remove_from_list, as postgresql raises error if position_column has non-null constraint
|
|
313
382
|
def store_at_0
|
|
314
383
|
if in_list?
|
|
315
384
|
old_position = send(position_column).to_i
|
|
316
|
-
|
|
385
|
+
set_list_position(0)
|
|
317
386
|
decrement_positions_on_lower_items(old_position)
|
|
318
387
|
end
|
|
319
388
|
end
|
|
320
|
-
|
|
389
|
+
|
|
390
|
+
def update_positions
|
|
391
|
+
old_position = send("#{position_column}_was").to_i
|
|
392
|
+
new_position = send(position_column).to_i
|
|
393
|
+
return unless acts_as_list_class.unscoped.where("#{scope_condition} AND #{position_column} = #{new_position}").count > 1
|
|
394
|
+
shuffle_positions_on_intermediate_items old_position, new_position, id
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def check_scope
|
|
398
|
+
if changes.include?("#{scope_name}")
|
|
399
|
+
old_scope_id = changes["#{scope_name}"].first
|
|
400
|
+
new_scope_id = changes["#{scope_name}"].last
|
|
401
|
+
self["#{scope_name}"] = old_scope_id
|
|
402
|
+
send("decrement_positions_on_lower_items")
|
|
403
|
+
self["#{scope_name}"] = new_scope_id
|
|
404
|
+
send("add_to_list_#{add_new_at}")
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def reload_position
|
|
409
|
+
self.reload
|
|
410
|
+
end
|
|
411
|
+
end
|
|
321
412
|
end
|
|
322
413
|
end
|
|
323
414
|
end
|
data/lib/acts_as_list/version.rb
CHANGED
data/lib/acts_as_list.rb
CHANGED
|
@@ -2,6 +2,7 @@ module Shared
|
|
|
2
2
|
module ArrayScopeList
|
|
3
3
|
def setup
|
|
4
4
|
(1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter, :parent_id => 5, :parent_type => 'ParentClass' }
|
|
5
|
+
(1..4).each { |counter| ArrayScopeListMixin.create! :pos => counter, :parent_id => 6, :parent_type => 'ParentClass' }
|
|
5
6
|
end
|
|
6
7
|
|
|
7
8
|
def test_reordering
|
|
@@ -24,6 +25,10 @@ module Shared
|
|
|
24
25
|
|
|
25
26
|
ArrayScopeListMixin.find(4).move_to_top
|
|
26
27
|
assert_equal [4, 1, 3, 2], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
|
28
|
+
|
|
29
|
+
ArrayScopeListMixin.find(4).insert_at(4)
|
|
30
|
+
assert_equal [1, 3, 2, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:id)
|
|
31
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.find(:all, :conditions => "parent_id = 5 AND parent_type = 'ParentClass'", :order => 'pos').map(&:pos)
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
def test_move_to_bottom_with_next_to_last_item
|
|
@@ -41,7 +46,6 @@ module Shared
|
|
|
41
46
|
|
|
42
47
|
def test_injection
|
|
43
48
|
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
49
|
assert_equal "pos", item.position_column
|
|
46
50
|
end
|
|
47
51
|
|
data/test/shared_list.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
module Shared
|
|
2
2
|
module List
|
|
3
3
|
def setup
|
|
4
|
-
(1..4).each
|
|
4
|
+
(1..4).each do |counter|
|
|
5
|
+
node = ListMixin.new :parent_id => 5
|
|
6
|
+
node.pos = counter
|
|
7
|
+
node.save!
|
|
8
|
+
end
|
|
5
9
|
end
|
|
6
10
|
|
|
7
11
|
def test_reordering
|
|
@@ -204,14 +208,18 @@ module Shared
|
|
|
204
208
|
def test_before_create_callback_adds_to_given_position
|
|
205
209
|
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
|
206
210
|
|
|
207
|
-
new = ListMixin.
|
|
211
|
+
new = ListMixin.new(:parent_id => 5)
|
|
212
|
+
new.pos = 1
|
|
213
|
+
new.save!
|
|
208
214
|
assert_equal 1, new.pos
|
|
209
215
|
assert new.first?
|
|
210
216
|
assert !new.last?
|
|
211
217
|
|
|
212
218
|
assert_equal [5, 1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
|
213
219
|
|
|
214
|
-
new = ListMixin.
|
|
220
|
+
new = ListMixin.new(:parent_id => 5)
|
|
221
|
+
new.pos = 3
|
|
222
|
+
new.save!
|
|
215
223
|
assert_equal 3, new.pos
|
|
216
224
|
assert !new.first?
|
|
217
225
|
assert !new.last?
|
data/test/shared_list_sub.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
module Shared
|
|
2
2
|
module ListSub
|
|
3
3
|
def setup
|
|
4
|
-
(1..4).each
|
|
4
|
+
(1..4).each do |i|
|
|
5
|
+
node = ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).new :parent_id => 5000
|
|
6
|
+
node.pos = i
|
|
7
|
+
node.save!
|
|
8
|
+
end
|
|
5
9
|
end
|
|
6
10
|
|
|
7
11
|
def test_reordering
|
|
@@ -39,6 +43,22 @@ module Shared
|
|
|
39
43
|
assert_nil ListMixin.find(4).lower_item
|
|
40
44
|
end
|
|
41
45
|
|
|
46
|
+
def test_next_prev_groups
|
|
47
|
+
li1 = ListMixin.find(1)
|
|
48
|
+
li2 = ListMixin.find(2)
|
|
49
|
+
li3 = ListMixin.find(3)
|
|
50
|
+
li4 = ListMixin.find(4)
|
|
51
|
+
assert_equal [li2, li3, li4], li1.lower_items
|
|
52
|
+
assert_equal [li4], li3.lower_items
|
|
53
|
+
assert_equal [li2, li3], li1.lower_items(2)
|
|
54
|
+
assert_equal [], li4.lower_items
|
|
55
|
+
|
|
56
|
+
assert_equal [li1, li2], li3.higher_items
|
|
57
|
+
assert_equal [li1], li2.higher_items
|
|
58
|
+
assert_equal [li2, li3], li4.higher_items(2)
|
|
59
|
+
assert_equal [], li1.higher_items
|
|
60
|
+
end
|
|
61
|
+
|
|
42
62
|
def test_injection
|
|
43
63
|
item = ListMixin.new("parent_id"=>1)
|
|
44
64
|
assert_equal '"mixins"."parent_id" = 1', item.scope_condition
|
data/test/test_list.rb
CHANGED
|
@@ -10,6 +10,7 @@ def setup_db(position_options = {})
|
|
|
10
10
|
ActiveRecord::Schema.define(:version => 1) do
|
|
11
11
|
create_table :mixins do |t|
|
|
12
12
|
t.column :pos, :integer, position_options
|
|
13
|
+
t.column :active, :boolean, :default => true
|
|
13
14
|
t.column :parent_id, :integer
|
|
14
15
|
t.column :parent_type, :string
|
|
15
16
|
t.column :created_at, :datetime
|
|
@@ -35,8 +36,27 @@ end
|
|
|
35
36
|
|
|
36
37
|
class Mixin < ActiveRecord::Base
|
|
37
38
|
self.table_name = 'mixins'
|
|
39
|
+
attr_accessible :active, :parent_id, :parent_type
|
|
38
40
|
end
|
|
39
41
|
|
|
42
|
+
class ProtectedMixin < ActiveRecord::Base
|
|
43
|
+
self.table_name = 'mixins'
|
|
44
|
+
attr_protected :active
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class ProtectedListMixin < ProtectedMixin
|
|
48
|
+
acts_as_list :column => "pos"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class UnProtectedMixin < ActiveRecord::Base
|
|
52
|
+
self.table_name = 'mixins'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class UnProtectedListMixin < UnProtectedMixin
|
|
56
|
+
acts_as_list :column => "pos"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
40
60
|
class ListMixin < Mixin
|
|
41
61
|
acts_as_list :column => "pos", :scope => :parent
|
|
42
62
|
end
|
|
@@ -69,6 +89,11 @@ class DefaultScopedMixin < Mixin
|
|
|
69
89
|
default_scope { order('pos ASC') }
|
|
70
90
|
end
|
|
71
91
|
|
|
92
|
+
class DefaultScopedWhereMixin < Mixin
|
|
93
|
+
acts_as_list :column => "pos"
|
|
94
|
+
default_scope { order('pos ASC').where(:active => true) }
|
|
95
|
+
end
|
|
96
|
+
|
|
72
97
|
class TopAdditionMixin < Mixin
|
|
73
98
|
acts_as_list :column => "pos", :add_new_at => :top, :scope => :parent_id
|
|
74
99
|
end
|
|
@@ -158,7 +183,7 @@ end
|
|
|
158
183
|
class DefaultScopedTest < ActsAsListTestCase
|
|
159
184
|
def setup
|
|
160
185
|
setup_db
|
|
161
|
-
(1..4).each { |counter| DefaultScopedMixin.create!
|
|
186
|
+
(1..4).each { |counter| DefaultScopedMixin.create!({:pos => counter}) }
|
|
162
187
|
end
|
|
163
188
|
|
|
164
189
|
def test_insert
|
|
@@ -235,18 +260,171 @@ class DefaultScopedTest < ActsAsListTestCase
|
|
|
235
260
|
assert_equal 4, new4.pos
|
|
236
261
|
end
|
|
237
262
|
|
|
263
|
+
def test_update_position
|
|
264
|
+
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
|
265
|
+
DefaultScopedMixin.find(2).set_list_position(4)
|
|
266
|
+
assert_equal [1, 3, 4, 2], DefaultScopedMixin.find(:all).map(&:id)
|
|
267
|
+
DefaultScopedMixin.find(2).set_list_position(2)
|
|
268
|
+
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
|
269
|
+
DefaultScopedMixin.find(1).set_list_position(4)
|
|
270
|
+
assert_equal [2, 3, 4, 1], DefaultScopedMixin.find(:all).map(&:id)
|
|
271
|
+
DefaultScopedMixin.find(1).set_list_position(1)
|
|
272
|
+
assert_equal [1, 2, 3, 4], DefaultScopedMixin.find(:all).map(&:id)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
class DefaultScopedWhereTest < ActsAsListTestCase
|
|
278
|
+
def setup
|
|
279
|
+
setup_db
|
|
280
|
+
(1..4).each { |counter| DefaultScopedWhereMixin.create! :pos => counter, :active => false }
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def test_insert
|
|
284
|
+
new = DefaultScopedWhereMixin.create
|
|
285
|
+
assert_equal 5, new.pos
|
|
286
|
+
assert !new.first?
|
|
287
|
+
assert new.last?
|
|
288
|
+
|
|
289
|
+
new = DefaultScopedWhereMixin.create
|
|
290
|
+
assert_equal 6, new.pos
|
|
291
|
+
assert !new.first?
|
|
292
|
+
assert new.last?
|
|
293
|
+
|
|
294
|
+
new = DefaultScopedWhereMixin.create
|
|
295
|
+
assert_equal 7, new.pos
|
|
296
|
+
assert !new.first?
|
|
297
|
+
assert new.last?
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def test_reordering
|
|
301
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).map(&:id)
|
|
302
|
+
|
|
303
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).move_lower
|
|
304
|
+
assert_equal [1, 3, 2, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
305
|
+
|
|
306
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).move_higher
|
|
307
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
308
|
+
|
|
309
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).move_to_bottom
|
|
310
|
+
assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
311
|
+
|
|
312
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).move_to_top
|
|
313
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
314
|
+
|
|
315
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).move_to_bottom
|
|
316
|
+
assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
317
|
+
|
|
318
|
+
DefaultScopedWhereMixin.where(:active => false).find(4).move_to_top
|
|
319
|
+
assert_equal [4, 1, 3, 2], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def test_insert_at
|
|
323
|
+
new = DefaultScopedWhereMixin.create
|
|
324
|
+
assert_equal 5, new.pos
|
|
325
|
+
|
|
326
|
+
new = DefaultScopedWhereMixin.create
|
|
327
|
+
assert_equal 6, new.pos
|
|
328
|
+
|
|
329
|
+
new = DefaultScopedWhereMixin.create
|
|
330
|
+
assert_equal 7, new.pos
|
|
331
|
+
|
|
332
|
+
new4 = DefaultScopedWhereMixin.create
|
|
333
|
+
assert_equal 8, new4.pos
|
|
334
|
+
|
|
335
|
+
new4.insert_at(2)
|
|
336
|
+
assert_equal 2, new4.pos
|
|
337
|
+
|
|
338
|
+
new.reload
|
|
339
|
+
assert_equal 8, new.pos
|
|
340
|
+
|
|
341
|
+
new.insert_at(2)
|
|
342
|
+
assert_equal 2, new.pos
|
|
343
|
+
|
|
344
|
+
new4.reload
|
|
345
|
+
assert_equal 3, new4.pos
|
|
346
|
+
|
|
347
|
+
new5 = DefaultScopedWhereMixin.create
|
|
348
|
+
assert_equal 9, new5.pos
|
|
349
|
+
|
|
350
|
+
new5.insert_at(1)
|
|
351
|
+
assert_equal 1, new5.pos
|
|
352
|
+
|
|
353
|
+
new4.reload
|
|
354
|
+
assert_equal 4, new4.pos
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def test_update_position
|
|
358
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
359
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).set_list_position(4)
|
|
360
|
+
assert_equal [1, 3, 4, 2], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
361
|
+
DefaultScopedWhereMixin.where(:active => false).find(2).set_list_position(2)
|
|
362
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
363
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).set_list_position(4)
|
|
364
|
+
assert_equal [2, 3, 4, 1], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
365
|
+
DefaultScopedWhereMixin.where(:active => false).find(1).set_list_position(1)
|
|
366
|
+
assert_equal [1, 2, 3, 4], DefaultScopedWhereMixin.where(:active => false).find(:all).map(&:id)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
class MultiDestroyTest < ActsAsListTestCase
|
|
372
|
+
|
|
373
|
+
def setup
|
|
374
|
+
setup_db
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# example:
|
|
378
|
+
#
|
|
379
|
+
# class TodoList < ActiveRecord::Base
|
|
380
|
+
# has_many :todo_items, :order => "position"
|
|
381
|
+
# accepts_nested_attributes_for :todo_items, :allow_destroy => true
|
|
382
|
+
# end
|
|
383
|
+
#
|
|
384
|
+
# class TodoItem < ActiveRecord::Base
|
|
385
|
+
# belongs_to :todo_list
|
|
386
|
+
# acts_as_list :scope => :todo_list
|
|
387
|
+
# end
|
|
388
|
+
#
|
|
389
|
+
# Assume that there are three items.
|
|
390
|
+
# The user mark two items as deleted, click save button, form will be post:
|
|
391
|
+
#
|
|
392
|
+
# todo_list.todo_items_attributes = [
|
|
393
|
+
# {id: 1, _destroy: true},
|
|
394
|
+
# {id: 2, _destroy: true}
|
|
395
|
+
# ]
|
|
396
|
+
#
|
|
397
|
+
# Save toto_list, the position of item #3 should eql 1.
|
|
398
|
+
#
|
|
399
|
+
def test_destroy
|
|
400
|
+
new1 = DefaultScopedMixin.create
|
|
401
|
+
assert_equal 1, new1.pos
|
|
402
|
+
|
|
403
|
+
new2 = DefaultScopedMixin.create
|
|
404
|
+
assert_equal 2, new2.pos
|
|
405
|
+
|
|
406
|
+
new3 = DefaultScopedMixin.create
|
|
407
|
+
assert_equal 3, new3.pos
|
|
408
|
+
|
|
409
|
+
new1.destroy
|
|
410
|
+
new2.destroy
|
|
411
|
+
new3.reload
|
|
412
|
+
assert_equal 1, new3.pos
|
|
413
|
+
end
|
|
238
414
|
end
|
|
239
415
|
|
|
240
416
|
#class TopAdditionMixin < Mixin
|
|
241
417
|
|
|
242
418
|
class TopAdditionTest < ActsAsListTestCase
|
|
243
419
|
include Shared::TopAddition
|
|
420
|
+
|
|
244
421
|
def setup
|
|
245
422
|
setup_db
|
|
246
423
|
super
|
|
247
424
|
end
|
|
248
425
|
end
|
|
249
426
|
|
|
427
|
+
|
|
250
428
|
class TopAdditionTestWithDefault < ActsAsListTestCase
|
|
251
429
|
include Shared::TopAddition
|
|
252
430
|
|
|
@@ -255,3 +433,44 @@ class TopAdditionTestWithDefault < ActsAsListTestCase
|
|
|
255
433
|
super
|
|
256
434
|
end
|
|
257
435
|
end
|
|
436
|
+
|
|
437
|
+
class RespectMixinProtection < ActsAsListTestCase
|
|
438
|
+
def setup
|
|
439
|
+
setup_db_with_default
|
|
440
|
+
super
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# if an attribute is set attr_protected
|
|
444
|
+
# it should be unchanged by update_attributes
|
|
445
|
+
def test_unmodified_protection
|
|
446
|
+
a = ProtectedMixin.new
|
|
447
|
+
a.update_attributes({:active => false})
|
|
448
|
+
assert_equal true, a.active
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# even after the acts_as_list mixin is joined
|
|
452
|
+
# that protection should continue to exist
|
|
453
|
+
def test_still_protected
|
|
454
|
+
b = ProtectedListMixin.new
|
|
455
|
+
b.update_attributes({:active => false})
|
|
456
|
+
assert_equal true, b.active
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# similarly, if a class lacks mass_assignment protection
|
|
460
|
+
# it should be able to be changed
|
|
461
|
+
def test_unprotected
|
|
462
|
+
a = UnProtectedMixin.new
|
|
463
|
+
a.update_attributes({:active => false})
|
|
464
|
+
assert_equal false, a.active
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# and it should continue to be mutable by mass_assignment
|
|
468
|
+
# even after the acts_as_list plugin has been joined
|
|
469
|
+
def test_still_unprotected_mixin
|
|
470
|
+
b = UnProtectedListMixin.new
|
|
471
|
+
b.assign_attributes({:active => false})
|
|
472
|
+
# p UnProtectedListMixin.accessible_attributes.length
|
|
473
|
+
assert_equal false, b.active
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
end
|
metadata
CHANGED
|
@@ -1,71 +1,93 @@
|
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: acts_as_list
|
|
3
|
-
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 23
|
|
5
5
|
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 2
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.2.0
|
|
6
11
|
platform: ruby
|
|
7
|
-
authors:
|
|
12
|
+
authors:
|
|
8
13
|
- David Heinemeier Hansson
|
|
9
14
|
- Swanand Pagnis
|
|
10
15
|
- Quinn Chaffee
|
|
11
16
|
autorequire:
|
|
12
17
|
bindir: bin
|
|
13
18
|
cert_chain: []
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
date: 2013-02-28 00:00:00 Z
|
|
21
|
+
dependencies:
|
|
22
|
+
- !ruby/object:Gem::Dependency
|
|
23
|
+
name: activerecord
|
|
24
|
+
prerelease: false
|
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
19
26
|
none: false
|
|
20
|
-
requirements:
|
|
21
|
-
- -
|
|
22
|
-
- !ruby/object:Gem::Version
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
requirements:
|
|
28
|
+
- - ">="
|
|
29
|
+
- !ruby/object:Gem::Version
|
|
30
|
+
hash: 7
|
|
31
|
+
segments:
|
|
32
|
+
- 3
|
|
33
|
+
- 0
|
|
34
|
+
version: "3.0"
|
|
35
|
+
type: :runtime
|
|
36
|
+
version_requirements: *id001
|
|
37
|
+
- !ruby/object:Gem::Dependency
|
|
38
|
+
name: bundler
|
|
25
39
|
prerelease: false
|
|
26
|
-
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: activerecord
|
|
29
|
-
requirement: &2152347320 !ruby/object:Gem::Requirement
|
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
30
41
|
none: false
|
|
31
|
-
requirements:
|
|
32
|
-
- -
|
|
33
|
-
- !ruby/object:Gem::Version
|
|
34
|
-
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
hash: 23
|
|
46
|
+
segments:
|
|
47
|
+
- 1
|
|
48
|
+
- 0
|
|
49
|
+
- 0
|
|
50
|
+
version: 1.0.0
|
|
35
51
|
type: :development
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- !ruby/object:Gem::Dependency
|
|
52
|
+
version_requirements: *id002
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
39
54
|
name: rdoc
|
|
40
|
-
|
|
55
|
+
prerelease: false
|
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
41
57
|
none: false
|
|
42
|
-
requirements:
|
|
43
|
-
- -
|
|
44
|
-
- !ruby/object:Gem::Version
|
|
45
|
-
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
hash: 3
|
|
62
|
+
segments:
|
|
63
|
+
- 0
|
|
64
|
+
version: "0"
|
|
46
65
|
type: :development
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- !ruby/object:Gem::Dependency
|
|
66
|
+
version_requirements: *id003
|
|
67
|
+
- !ruby/object:Gem::Dependency
|
|
50
68
|
name: sqlite3
|
|
51
|
-
|
|
69
|
+
prerelease: false
|
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
|
52
71
|
none: false
|
|
53
|
-
requirements:
|
|
54
|
-
- -
|
|
55
|
-
- !ruby/object:Gem::Version
|
|
56
|
-
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
hash: 3
|
|
76
|
+
segments:
|
|
77
|
+
- 0
|
|
78
|
+
version: "0"
|
|
57
79
|
type: :development
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
a number of objects in a list. The class that has this specified needs to have a
|
|
62
|
-
"position" column defined as an integer on the mapped database table.
|
|
63
|
-
email:
|
|
80
|
+
version_requirements: *id004
|
|
81
|
+
description: 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.
|
|
82
|
+
email:
|
|
64
83
|
- swanand.pagnis@gmail.com
|
|
65
84
|
executables: []
|
|
85
|
+
|
|
66
86
|
extensions: []
|
|
87
|
+
|
|
67
88
|
extra_rdoc_files: []
|
|
68
|
-
|
|
89
|
+
|
|
90
|
+
files:
|
|
69
91
|
- .gemtest
|
|
70
92
|
- .gitignore
|
|
71
93
|
- .travis.yml
|
|
@@ -87,29 +109,38 @@ files:
|
|
|
87
109
|
- test/test_list.rb
|
|
88
110
|
homepage: http://github.com/swanandp/acts_as_list
|
|
89
111
|
licenses: []
|
|
112
|
+
|
|
90
113
|
post_install_message:
|
|
91
114
|
rdoc_options: []
|
|
92
|
-
|
|
115
|
+
|
|
116
|
+
require_paths:
|
|
93
117
|
- lib
|
|
94
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
119
|
none: false
|
|
96
|
-
requirements:
|
|
97
|
-
- -
|
|
98
|
-
- !ruby/object:Gem::Version
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
hash: 3
|
|
124
|
+
segments:
|
|
125
|
+
- 0
|
|
126
|
+
version: "0"
|
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
128
|
none: false
|
|
102
|
-
requirements:
|
|
103
|
-
- -
|
|
104
|
-
- !ruby/object:Gem::Version
|
|
105
|
-
|
|
129
|
+
requirements:
|
|
130
|
+
- - ">="
|
|
131
|
+
- !ruby/object:Gem::Version
|
|
132
|
+
hash: 3
|
|
133
|
+
segments:
|
|
134
|
+
- 0
|
|
135
|
+
version: "0"
|
|
106
136
|
requirements: []
|
|
137
|
+
|
|
107
138
|
rubyforge_project: acts_as_list
|
|
108
139
|
rubygems_version: 1.8.15
|
|
109
140
|
signing_key:
|
|
110
141
|
specification_version: 3
|
|
111
142
|
summary: A gem allowing a active_record model to act_as_list.
|
|
112
|
-
test_files:
|
|
143
|
+
test_files:
|
|
113
144
|
- test/helper.rb
|
|
114
145
|
- test/shared.rb
|
|
115
146
|
- test/shared_array_scope_list.rb
|