acts_as_list 0.1.9 → 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 +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
|