acts_as_list 0.1.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/README.md +52 -9
- data/acts_as_list.gemspec +1 -1
- data/lib/acts_as_list/active_record/acts/list.rb +80 -17
- data/lib/acts_as_list/version.rb +1 -1
- data/test/shared_list_sub.rb +16 -0
- metadata +88 -57
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -16,23 +16,66 @@ Or, from the command line:
|
|
16
16
|
|
17
17
|
## Example
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
27
30
|
|
28
|
-
|
29
|
-
|
31
|
+
class TodoItem < ActiveRecord::Base
|
32
|
+
belongs_to :todo_list
|
33
|
+
acts_as_list scope: :todo_list
|
34
|
+
end
|
30
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
|
+
|
31
72
|
## Notes
|
32
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.
|
33
74
|
|
34
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.
|
35
76
|
|
77
|
+
The `position` column is set after validations are called, so you should not put a `presence` validation on the `position` column.
|
78
|
+
|
36
79
|
## Versions
|
37
80
|
All versions `0.1.5` onwards require Rails 3.0.x and higher.
|
38
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
|
@@ -73,12 +73,20 @@ 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
|
|
78
86
|
# only add to attr_accessible
|
79
87
|
# if the class has some mass_assignment_protection
|
80
88
|
|
81
|
-
|
89
|
+
if defined?(accessible_attributes) and !accessible_attributes.blank?
|
82
90
|
attr_accessible :#{configuration[:column]}
|
83
91
|
end
|
84
92
|
|
@@ -86,6 +94,7 @@ module ActiveRecord
|
|
86
94
|
after_destroy :decrement_positions_on_lower_items
|
87
95
|
before_create :add_to_list_#{configuration[:add_new_at]}
|
88
96
|
after_update :update_positions
|
97
|
+
before_update :check_scope
|
89
98
|
EOV
|
90
99
|
end
|
91
100
|
end
|
@@ -181,6 +190,18 @@ module ActiveRecord
|
|
181
190
|
)
|
182
191
|
end
|
183
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
|
+
|
184
205
|
# Return the next lower item in the list.
|
185
206
|
def lower_item
|
186
207
|
return nil unless in_list?
|
@@ -190,6 +211,18 @@ module ActiveRecord
|
|
190
211
|
)
|
191
212
|
end
|
192
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
|
+
|
193
226
|
# Test if this record is in a list
|
194
227
|
def in_list?
|
195
228
|
!not_in_list?
|
@@ -214,6 +247,11 @@ module ActiveRecord
|
|
214
247
|
end
|
215
248
|
|
216
249
|
private
|
250
|
+
def acts_as_list_list
|
251
|
+
acts_as_list_class.unscoped.
|
252
|
+
where(scope_condition)
|
253
|
+
end
|
254
|
+
|
217
255
|
def add_to_list_top
|
218
256
|
increment_positions_on_all_items
|
219
257
|
self[position_column] = acts_as_list_top
|
@@ -241,7 +279,7 @@ module ActiveRecord
|
|
241
279
|
def bottom_item(except = nil)
|
242
280
|
conditions = scope_condition
|
243
281
|
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
|
244
|
-
acts_as_list_class.unscoped.
|
282
|
+
acts_as_list_class.unscoped.where(conditions).order("#{acts_as_list_class.table_name}.#{position_column} DESC").first
|
245
283
|
end
|
246
284
|
|
247
285
|
# Forces item to assume the bottom position in the list.
|
@@ -256,8 +294,10 @@ module ActiveRecord
|
|
256
294
|
|
257
295
|
# This has the effect of moving all the higher items up one.
|
258
296
|
def decrement_positions_on_higher_items(position)
|
259
|
-
acts_as_list_class.unscoped.
|
260
|
-
"#{
|
297
|
+
acts_as_list_class.unscoped.where(
|
298
|
+
"#{scope_condition} AND #{position_column} <= #{position}"
|
299
|
+
).update_all(
|
300
|
+
"#{position_column} = (#{position_column} - 1)"
|
261
301
|
)
|
262
302
|
end
|
263
303
|
|
@@ -265,30 +305,38 @@ module ActiveRecord
|
|
265
305
|
def decrement_positions_on_lower_items(position=nil)
|
266
306
|
return unless in_list?
|
267
307
|
position ||= send(position_column).to_i
|
268
|
-
acts_as_list_class.unscoped.
|
269
|
-
"#{
|
308
|
+
acts_as_list_class.unscoped.where(
|
309
|
+
"#{scope_condition} AND #{position_column} > #{position}"
|
310
|
+
).update_all(
|
311
|
+
"#{position_column} = (#{position_column} - 1)"
|
270
312
|
)
|
271
313
|
end
|
272
314
|
|
273
315
|
# This has the effect of moving all the higher items down one.
|
274
316
|
def increment_positions_on_higher_items
|
275
317
|
return unless in_list?
|
276
|
-
acts_as_list_class.unscoped.
|
277
|
-
"#{
|
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)"
|
278
322
|
)
|
279
323
|
end
|
280
324
|
|
281
325
|
# This has the effect of moving all the lower items down one.
|
282
326
|
def increment_positions_on_lower_items(position)
|
283
|
-
acts_as_list_class.unscoped.
|
284
|
-
"#{
|
285
|
-
|
327
|
+
acts_as_list_class.unscoped.where(
|
328
|
+
"#{scope_condition} AND #{position_column} >= #{position}"
|
329
|
+
).update_all(
|
330
|
+
"#{position_column} = (#{position_column} + 1)"
|
331
|
+
)
|
286
332
|
end
|
287
333
|
|
288
334
|
# Increments position (<tt>position_column</tt>) of all items in the list.
|
289
335
|
def increment_positions_on_all_items
|
290
|
-
acts_as_list_class.unscoped.
|
291
|
-
"#{
|
336
|
+
acts_as_list_class.unscoped.where(
|
337
|
+
"#{scope_condition}"
|
338
|
+
).update_all(
|
339
|
+
"#{position_column} = (#{position_column} + 1)"
|
292
340
|
)
|
293
341
|
end
|
294
342
|
|
@@ -301,16 +349,20 @@ module ActiveRecord
|
|
301
349
|
#
|
302
350
|
# e.g., if moving an item from 2 to 5,
|
303
351
|
# move [3, 4, 5] to [2, 3, 4]
|
304
|
-
acts_as_list_class.unscoped.
|
305
|
-
"#{
|
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)"
|
306
356
|
)
|
307
357
|
else
|
308
358
|
# Increment position of intermediate items
|
309
359
|
#
|
310
360
|
# e.g., if moving an item from 5 to 2,
|
311
361
|
# move [2, 3, 4] to [3, 4, 5]
|
312
|
-
acts_as_list_class.unscoped.
|
313
|
-
"#{
|
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)"
|
314
366
|
)
|
315
367
|
end
|
316
368
|
end
|
@@ -342,6 +394,17 @@ module ActiveRecord
|
|
342
394
|
shuffle_positions_on_intermediate_items old_position, new_position, id
|
343
395
|
end
|
344
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
|
+
|
345
408
|
def reload_position
|
346
409
|
self.reload
|
347
410
|
end
|
data/lib/acts_as_list/version.rb
CHANGED
data/test/shared_list_sub.rb
CHANGED
@@ -43,6 +43,22 @@ module Shared
|
|
43
43
|
assert_nil ListMixin.find(4).lower_item
|
44
44
|
end
|
45
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
|
+
|
46
62
|
def test_injection
|
47
63
|
item = ListMixin.new("parent_id"=>1)
|
48
64
|
assert_equal '"mixins"."parent_id" = 1', item.scope_condition
|
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: &2155910780 !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
|