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 CHANGED
@@ -4,3 +4,4 @@ Gemfile.lock
4
4
  pkg/*
5
5
  .rvmrc
6
6
  *.tmproj
7
+ .rbenv-version
@@ -3,3 +3,4 @@ rvm:
3
3
  - 1.8.7
4
4
  - 1.9.2
5
5
  - 1.9.3
6
+ - 2.0.0
data/README.md CHANGED
@@ -16,23 +16,66 @@ Or, from the command line:
16
16
 
17
17
  ## Example
18
18
 
19
- class TodoList < ActiveRecord::Base
20
- has_many :todo_items, :order => "position"
21
- end
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
- class TodoItem < ActiveRecord::Base
24
- belongs_to :todo_list
25
- acts_as_list :scope => :todo_list
26
- end
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
- todo_list.first.move_to_bottom
29
- todo_list.last.move_higher
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
 
@@ -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
- unless accessible_attributes.blank?
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.find(:first, :conditions => conditions, :order => "#{acts_as_list_class.table_name}.#{position_column} DESC")
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.update_all(
260
- "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
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.update_all(
269
- "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{position}"
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.update_all(
277
- "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
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.update_all(
284
- "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
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.update_all(
291
- "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
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.update_all(
305
- "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{old_position} AND #{position_column} <= #{new_position}#{avoid_id_condition}"
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.update_all(
313
- "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{new_position} AND #{position_column} < #{old_position}#{avoid_id_condition}"
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
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Acts
3
3
  module List
4
- VERSION = "0.1.9"
4
+ VERSION = "0.2.0"
5
5
  end
6
6
  end
7
7
  end
@@ -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
- version: 0.1.9
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
- date: 2012-12-04 00:00:00.000000000 Z
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
17
- name: bundler
18
- requirement: &2155911360 !ruby/object:Gem::Requirement
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
- version: 1.0.0
24
- type: :development
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
- version_requirements: *2155911360
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
- version: 1.15.4.7794
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
- prerelease: false
37
- version_requirements: *2155910780
38
- - !ruby/object:Gem::Dependency
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
39
54
  name: rdoc
40
- requirement: &2155910340 !ruby/object:Gem::Requirement
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
41
57
  none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: '0'
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
46
65
  type: :development
47
- prerelease: false
48
- version_requirements: *2155910340
49
- - !ruby/object:Gem::Dependency
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
50
68
  name: sqlite3
51
- requirement: &2155909700 !ruby/object:Gem::Requirement
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
52
71
  none: false
53
- requirements:
54
- - - ! '>='
55
- - !ruby/object:Gem::Version
56
- version: '0'
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
57
79
  type: :development
58
- prerelease: false
59
- version_requirements: *2155909700
60
- description: This "acts_as" extension provides the capabilities for sorting and reordering
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
- files:
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
- require_paths:
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
- version: '0'
100
- required_rubygems_version: !ruby/object:Gem::Requirement
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
- version: '0'
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