ryanb-acts-as-list 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.
data/Manifest ADDED
@@ -0,0 +1,6 @@
1
+ acts-as-list.gemspec
2
+ lib/acts_as_list.rb
3
+ Manifest
4
+ README
5
+ tasks/deployment.rake
6
+ test/list_test.rb
data/README ADDED
@@ -0,0 +1,34 @@
1
+ ActsAsList
2
+ ==========
3
+
4
+ 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.
5
+
6
+ Install
7
+ =======
8
+
9
+ Specify it in your Rails config.
10
+
11
+ config.gem 'ryanb-acts-as-list', :lib => 'acts_as_list', :source => 'http://gems.github.com'
12
+
13
+ Then install it.
14
+
15
+ rake gems:install
16
+
17
+
18
+ Example
19
+ =======
20
+
21
+ class TodoList < ActiveRecord::Base
22
+ has_many :todo_items, :order => "position"
23
+ end
24
+
25
+ class TodoItem < ActiveRecord::Base
26
+ belongs_to :todo_list
27
+ acts_as_list :scope => :todo_list
28
+ end
29
+
30
+ todo_list.first.move_to_bottom
31
+ todo_list.last.move_higher
32
+
33
+
34
+ Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
@@ -0,0 +1,58 @@
1
+
2
+ # Gem::Specification for Acts-as-list-0.1.0
3
+ # Originally generated by Echoe
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{acts-as-list}
7
+ s.version = "0.1.0"
8
+
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.authors = ["Ryan Bates, Rails Core"]
11
+ s.date = %q{2008-07-01}
12
+ s.description = %q{Gem version of acts_as_list Rails plugin.}
13
+ s.email = %q{ryan (at) railscasts (dot) com}
14
+ s.extra_rdoc_files = ["lib/acts_as_list.rb", "README", "tasks/deployment.rake"]
15
+ s.files = ["lib/acts_as_list.rb", "Manifest", "README", "tasks/deployment.rake", "test/list_test.rb", "acts-as-list.gemspec"]
16
+ s.has_rdoc = true
17
+ s.homepage = %q{http://github.com/ryanb/acts-as-list}
18
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Acts-as-list", "--main", "README"]
19
+ s.require_paths = ["lib"]
20
+ s.rubyforge_project = %q{acts-as-list}
21
+ s.rubygems_version = %q{1.2.0}
22
+ s.summary = %q{Gem version of acts_as_list Rails plugin.}
23
+ s.test_files = ["test/list_test.rb"]
24
+
25
+ if s.respond_to? :specification_version then
26
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
27
+ s.specification_version = 2
28
+
29
+ if current_version >= 3 then
30
+ else
31
+ end
32
+ else
33
+ end
34
+ end
35
+
36
+
37
+ # # Original Rakefile source (requires the Echoe gem):
38
+ #
39
+ # require 'rubygems'
40
+ # require 'rake'
41
+ #
42
+ # begin
43
+ # require 'echoe'
44
+ #
45
+ # Echoe.new('acts-as-list', '0.1.0') do |p|
46
+ # p.summary = "Gem version of acts_as_list Rails plugin."
47
+ # p.description = "Gem version of acts_as_list Rails plugin."
48
+ # p.url = "http://github.com/ryanb/acts-as-list"
49
+ # p.author = ['Ryan Bates', 'Rails Core']
50
+ # p.email = "ryan (at) railscasts (dot) com"
51
+ # end
52
+ #
53
+ # rescue LoadError => boom
54
+ # puts "You are missing a dependency required for meta-operations on this gem."
55
+ # puts "#{boom.to_s.capitalize}."
56
+ # end
57
+ #
58
+ # Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
@@ -0,0 +1,254 @@
1
+ module ActsAsList
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ end
5
+
6
+ # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
7
+ # The class that has this specified needs to have a +position+ column defined as an integer on
8
+ # the mapped database table.
9
+ #
10
+ # Todo list example:
11
+ #
12
+ # class TodoList < ActiveRecord::Base
13
+ # has_many :todo_items, :order => "position"
14
+ # end
15
+ #
16
+ # class TodoItem < ActiveRecord::Base
17
+ # belongs_to :todo_list
18
+ # acts_as_list :scope => :todo_list
19
+ # end
20
+ #
21
+ # todo_list.first.move_to_bottom
22
+ # todo_list.last.move_higher
23
+ module ClassMethods
24
+ # Configuration options are:
25
+ #
26
+ # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
27
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
28
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
29
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
30
+ # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
31
+ def acts_as_list(options = {})
32
+ configuration = { :column => "position", :scope => "1 = 1" }
33
+ configuration.update(options) if options.is_a?(Hash)
34
+
35
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
36
+
37
+ if configuration[:scope].is_a?(Symbol)
38
+ scope_condition_method = %(
39
+ def scope_condition
40
+ if #{configuration[:scope].to_s}.nil?
41
+ "#{configuration[:scope].to_s} IS NULL"
42
+ else
43
+ "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
44
+ end
45
+ end
46
+ )
47
+ else
48
+ scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
49
+ end
50
+
51
+ class_eval <<-EOV
52
+ include ActsAsList::InstanceMethods
53
+
54
+ def acts_as_list_class
55
+ ::#{self.name}
56
+ end
57
+
58
+ def position_column
59
+ '#{configuration[:column]}'
60
+ end
61
+
62
+ #{scope_condition_method}
63
+
64
+ before_destroy :remove_from_list
65
+ before_create :add_to_list_bottom
66
+ EOV
67
+ end
68
+ end
69
+
70
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
71
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
72
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
73
+ # the first in the list of all chapters.
74
+ module InstanceMethods
75
+ # Insert the item at the given position (defaults to the top position of 1).
76
+ def insert_at(position = 1)
77
+ insert_at_position(position)
78
+ end
79
+
80
+ # Swap positions with the next lower item, if one exists.
81
+ def move_lower
82
+ return unless lower_item
83
+
84
+ acts_as_list_class.transaction do
85
+ lower_item.decrement_position
86
+ increment_position
87
+ end
88
+ end
89
+
90
+ # Swap positions with the next higher item, if one exists.
91
+ def move_higher
92
+ return unless higher_item
93
+
94
+ acts_as_list_class.transaction do
95
+ higher_item.increment_position
96
+ decrement_position
97
+ end
98
+ end
99
+
100
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
101
+ # position adjusted accordingly.
102
+ def move_to_bottom
103
+ return unless in_list?
104
+ acts_as_list_class.transaction do
105
+ decrement_positions_on_lower_items
106
+ assume_bottom_position
107
+ end
108
+ end
109
+
110
+ # Move to the top of the list. If the item is already in the list, the items above it have their
111
+ # position adjusted accordingly.
112
+ def move_to_top
113
+ return unless in_list?
114
+ acts_as_list_class.transaction do
115
+ increment_positions_on_higher_items
116
+ assume_top_position
117
+ end
118
+ end
119
+
120
+ # Removes the item from the list.
121
+ def remove_from_list
122
+ if in_list?
123
+ decrement_positions_on_lower_items
124
+ update_attribute position_column, nil
125
+ end
126
+ end
127
+
128
+ # Increase the position of this item without adjusting the rest of the list.
129
+ def increment_position
130
+ return unless in_list?
131
+ update_attribute position_column, self.send(position_column).to_i + 1
132
+ end
133
+
134
+ # Decrease the position of this item without adjusting the rest of the list.
135
+ def decrement_position
136
+ return unless in_list?
137
+ update_attribute position_column, self.send(position_column).to_i - 1
138
+ end
139
+
140
+ # Return +true+ if this object is the first in the list.
141
+ def first?
142
+ return false unless in_list?
143
+ self.send(position_column) == 1
144
+ end
145
+
146
+ # Return +true+ if this object is the last in the list.
147
+ def last?
148
+ return false unless in_list?
149
+ self.send(position_column) == bottom_position_in_list
150
+ end
151
+
152
+ # Return the next higher item in the list.
153
+ def higher_item
154
+ return nil unless in_list?
155
+ acts_as_list_class.find(:first, :conditions =>
156
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
157
+ )
158
+ end
159
+
160
+ # Return the next lower item in the list.
161
+ def lower_item
162
+ return nil unless in_list?
163
+ acts_as_list_class.find(:first, :conditions =>
164
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
165
+ )
166
+ end
167
+
168
+ # Test if this record is in a list
169
+ def in_list?
170
+ !send(position_column).nil?
171
+ end
172
+
173
+ private
174
+ def add_to_list_top
175
+ increment_positions_on_all_items
176
+ end
177
+
178
+ def add_to_list_bottom
179
+ self[position_column] = bottom_position_in_list.to_i + 1
180
+ end
181
+
182
+ # Overwrite this method to define the scope of the list changes
183
+ def scope_condition() "1" end
184
+
185
+ # Returns the bottom position number in the list.
186
+ # bottom_position_in_list # => 2
187
+ def bottom_position_in_list(except = nil)
188
+ item = bottom_item(except)
189
+ item ? item.send(position_column) : 0
190
+ end
191
+
192
+ # Returns the bottom item
193
+ def bottom_item(except = nil)
194
+ conditions = scope_condition
195
+ conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
196
+ acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
197
+ end
198
+
199
+ # Forces item to assume the bottom position in the list.
200
+ def assume_bottom_position
201
+ update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
202
+ end
203
+
204
+ # Forces item to assume the top position in the list.
205
+ def assume_top_position
206
+ update_attribute(position_column, 1)
207
+ end
208
+
209
+ # This has the effect of moving all the higher items up one.
210
+ def decrement_positions_on_higher_items(position)
211
+ acts_as_list_class.update_all(
212
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
213
+ )
214
+ end
215
+
216
+ # This has the effect of moving all the lower items up one.
217
+ def decrement_positions_on_lower_items
218
+ return unless in_list?
219
+ acts_as_list_class.update_all(
220
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
221
+ )
222
+ end
223
+
224
+ # This has the effect of moving all the higher items down one.
225
+ def increment_positions_on_higher_items
226
+ return unless in_list?
227
+ acts_as_list_class.update_all(
228
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
229
+ )
230
+ end
231
+
232
+ # This has the effect of moving all the lower items down one.
233
+ def increment_positions_on_lower_items(position)
234
+ acts_as_list_class.update_all(
235
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
236
+ )
237
+ end
238
+
239
+ # Increments position (<tt>position_column</tt>) of all items in the list.
240
+ def increment_positions_on_all_items
241
+ acts_as_list_class.update_all(
242
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
243
+ )
244
+ end
245
+
246
+ def insert_at_position(position)
247
+ remove_from_list
248
+ increment_positions_on_lower_items(position)
249
+ self.update_attribute(position_column, position)
250
+ end
251
+ end
252
+ end
253
+
254
+ ActiveRecord::Base.class_eval { include ActsAsList }
@@ -0,0 +1,2 @@
1
+ desc "Build the manifest and gemspec files."
2
+ task :build => [:build_manifest, :build_gemspec]
data/test/list_test.rb ADDED
@@ -0,0 +1,332 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+ gem 'activerecord', '>= 1.15.4.7794'
5
+ require 'active_record'
6
+
7
+ require "#{File.dirname(__FILE__)}/../lib/acts_as_list"
8
+
9
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
10
+
11
+ def setup_db
12
+ ActiveRecord::Schema.define(:version => 1) do
13
+ create_table :mixins do |t|
14
+ t.column :pos, :integer
15
+ t.column :parent_id, :integer
16
+ t.column :created_at, :datetime
17
+ t.column :updated_at, :datetime
18
+ end
19
+ end
20
+ end
21
+
22
+ def teardown_db
23
+ ActiveRecord::Base.connection.tables.each do |table|
24
+ ActiveRecord::Base.connection.drop_table(table)
25
+ end
26
+ end
27
+
28
+ class Mixin < ActiveRecord::Base
29
+ end
30
+
31
+ class ListMixin < Mixin
32
+ acts_as_list :column => "pos", :scope => :parent
33
+
34
+ def self.table_name() "mixins" end
35
+ end
36
+
37
+ class ListMixinSub1 < ListMixin
38
+ end
39
+
40
+ class ListMixinSub2 < ListMixin
41
+ end
42
+
43
+ class ListWithStringScopeMixin < ActiveRecord::Base
44
+ acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
45
+
46
+ def self.table_name() "mixins" end
47
+ end
48
+
49
+
50
+ class ListTest < Test::Unit::TestCase
51
+
52
+ def setup
53
+ setup_db
54
+ (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
55
+ end
56
+
57
+ def teardown
58
+ teardown_db
59
+ end
60
+
61
+ def test_reordering
62
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
63
+
64
+ ListMixin.find(2).move_lower
65
+ assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
66
+
67
+ ListMixin.find(2).move_higher
68
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
69
+
70
+ ListMixin.find(1).move_to_bottom
71
+ assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
72
+
73
+ ListMixin.find(1).move_to_top
74
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
75
+
76
+ ListMixin.find(2).move_to_bottom
77
+ assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
78
+
79
+ ListMixin.find(4).move_to_top
80
+ assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
81
+ end
82
+
83
+ def test_move_to_bottom_with_next_to_last_item
84
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
85
+ ListMixin.find(3).move_to_bottom
86
+ assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
87
+ end
88
+
89
+ def test_next_prev
90
+ assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
91
+ assert_nil ListMixin.find(1).higher_item
92
+ assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
93
+ assert_nil ListMixin.find(4).lower_item
94
+ end
95
+
96
+ def test_injection
97
+ item = ListMixin.new(:parent_id => 1)
98
+ assert_equal "parent_id = 1", item.scope_condition
99
+ assert_equal "pos", item.position_column
100
+ end
101
+
102
+ def test_insert
103
+ new = ListMixin.create(:parent_id => 20)
104
+ assert_equal 1, new.pos
105
+ assert new.first?
106
+ assert new.last?
107
+
108
+ new = ListMixin.create(:parent_id => 20)
109
+ assert_equal 2, new.pos
110
+ assert !new.first?
111
+ assert new.last?
112
+
113
+ new = ListMixin.create(:parent_id => 20)
114
+ assert_equal 3, new.pos
115
+ assert !new.first?
116
+ assert new.last?
117
+
118
+ new = ListMixin.create(:parent_id => 0)
119
+ assert_equal 1, new.pos
120
+ assert new.first?
121
+ assert new.last?
122
+ end
123
+
124
+ def test_insert_at
125
+ new = ListMixin.create(:parent_id => 20)
126
+ assert_equal 1, new.pos
127
+
128
+ new = ListMixin.create(:parent_id => 20)
129
+ assert_equal 2, new.pos
130
+
131
+ new = ListMixin.create(:parent_id => 20)
132
+ assert_equal 3, new.pos
133
+
134
+ new4 = ListMixin.create(:parent_id => 20)
135
+ assert_equal 4, new4.pos
136
+
137
+ new4.insert_at(3)
138
+ assert_equal 3, new4.pos
139
+
140
+ new.reload
141
+ assert_equal 4, new.pos
142
+
143
+ new.insert_at(2)
144
+ assert_equal 2, new.pos
145
+
146
+ new4.reload
147
+ assert_equal 4, new4.pos
148
+
149
+ new5 = ListMixin.create(:parent_id => 20)
150
+ assert_equal 5, new5.pos
151
+
152
+ new5.insert_at(1)
153
+ assert_equal 1, new5.pos
154
+
155
+ new4.reload
156
+ assert_equal 5, new4.pos
157
+ end
158
+
159
+ def test_delete_middle
160
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
161
+
162
+ ListMixin.find(2).destroy
163
+
164
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
165
+
166
+ assert_equal 1, ListMixin.find(1).pos
167
+ assert_equal 2, ListMixin.find(3).pos
168
+ assert_equal 3, ListMixin.find(4).pos
169
+
170
+ ListMixin.find(1).destroy
171
+
172
+ assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
173
+
174
+ assert_equal 1, ListMixin.find(3).pos
175
+ assert_equal 2, ListMixin.find(4).pos
176
+ end
177
+
178
+ def test_with_string_based_scope
179
+ new = ListWithStringScopeMixin.create(:parent_id => 500)
180
+ assert_equal 1, new.pos
181
+ assert new.first?
182
+ assert new.last?
183
+ end
184
+
185
+ def test_nil_scope
186
+ new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
187
+ new2.move_higher
188
+ assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
189
+ end
190
+
191
+
192
+ def test_remove_from_list_should_then_fail_in_list?
193
+ assert_equal true, ListMixin.find(1).in_list?
194
+ ListMixin.find(1).remove_from_list
195
+ assert_equal false, ListMixin.find(1).in_list?
196
+ end
197
+
198
+ def test_remove_from_list_should_set_position_to_nil
199
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
200
+
201
+ ListMixin.find(2).remove_from_list
202
+
203
+ assert_equal [2, 1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
204
+
205
+ assert_equal 1, ListMixin.find(1).pos
206
+ assert_equal nil, ListMixin.find(2).pos
207
+ assert_equal 2, ListMixin.find(3).pos
208
+ assert_equal 3, ListMixin.find(4).pos
209
+ end
210
+
211
+ def test_remove_before_destroy_does_not_shift_lower_items_twice
212
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
213
+
214
+ ListMixin.find(2).remove_from_list
215
+ ListMixin.find(2).destroy
216
+
217
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
218
+
219
+ assert_equal 1, ListMixin.find(1).pos
220
+ assert_equal 2, ListMixin.find(3).pos
221
+ assert_equal 3, ListMixin.find(4).pos
222
+ end
223
+
224
+ end
225
+
226
+ class ListSubTest < Test::Unit::TestCase
227
+
228
+ def setup
229
+ setup_db
230
+ (1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
231
+ end
232
+
233
+ def teardown
234
+ teardown_db
235
+ end
236
+
237
+ def test_reordering
238
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
239
+
240
+ ListMixin.find(2).move_lower
241
+ assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
242
+
243
+ ListMixin.find(2).move_higher
244
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
245
+
246
+ ListMixin.find(1).move_to_bottom
247
+ assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
248
+
249
+ ListMixin.find(1).move_to_top
250
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
251
+
252
+ ListMixin.find(2).move_to_bottom
253
+ assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
254
+
255
+ ListMixin.find(4).move_to_top
256
+ assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
257
+ end
258
+
259
+ def test_move_to_bottom_with_next_to_last_item
260
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
261
+ ListMixin.find(3).move_to_bottom
262
+ assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
263
+ end
264
+
265
+ def test_next_prev
266
+ assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
267
+ assert_nil ListMixin.find(1).higher_item
268
+ assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
269
+ assert_nil ListMixin.find(4).lower_item
270
+ end
271
+
272
+ def test_injection
273
+ item = ListMixin.new("parent_id"=>1)
274
+ assert_equal "parent_id = 1", item.scope_condition
275
+ assert_equal "pos", item.position_column
276
+ end
277
+
278
+ def test_insert_at
279
+ new = ListMixin.create("parent_id" => 20)
280
+ assert_equal 1, new.pos
281
+
282
+ new = ListMixinSub1.create("parent_id" => 20)
283
+ assert_equal 2, new.pos
284
+
285
+ new = ListMixinSub2.create("parent_id" => 20)
286
+ assert_equal 3, new.pos
287
+
288
+ new4 = ListMixin.create("parent_id" => 20)
289
+ assert_equal 4, new4.pos
290
+
291
+ new4.insert_at(3)
292
+ assert_equal 3, new4.pos
293
+
294
+ new.reload
295
+ assert_equal 4, new.pos
296
+
297
+ new.insert_at(2)
298
+ assert_equal 2, new.pos
299
+
300
+ new4.reload
301
+ assert_equal 4, new4.pos
302
+
303
+ new5 = ListMixinSub1.create("parent_id" => 20)
304
+ assert_equal 5, new5.pos
305
+
306
+ new5.insert_at(1)
307
+ assert_equal 1, new5.pos
308
+
309
+ new4.reload
310
+ assert_equal 5, new4.pos
311
+ end
312
+
313
+ def test_delete_middle
314
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
315
+
316
+ ListMixin.find(2).destroy
317
+
318
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
319
+
320
+ assert_equal 1, ListMixin.find(1).pos
321
+ assert_equal 2, ListMixin.find(3).pos
322
+ assert_equal 3, ListMixin.find(4).pos
323
+
324
+ ListMixin.find(1).destroy
325
+
326
+ assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
327
+
328
+ assert_equal 1, ListMixin.find(3).pos
329
+ assert_equal 2, ListMixin.find(4).pos
330
+ end
331
+
332
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ryanb-acts-as-list
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Bates, Rails Core
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-01 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Gem version of acts_as_list Rails plugin.
17
+ email: ryan (at) railscasts (dot) com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - lib/acts_as_list.rb
24
+ - README
25
+ - tasks/deployment.rake
26
+ files:
27
+ - lib/acts_as_list.rb
28
+ - Manifest
29
+ - README
30
+ - tasks/deployment.rake
31
+ - test/list_test.rb
32
+ - acts-as-list.gemspec
33
+ has_rdoc: true
34
+ homepage: http://github.com/ryanb/acts-as-list
35
+ post_install_message:
36
+ rdoc_options:
37
+ - --line-numbers
38
+ - --inline-source
39
+ - --title
40
+ - Acts-as-list
41
+ - --main
42
+ - README
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project: acts-as-list
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: Gem version of acts_as_list Rails plugin.
64
+ test_files:
65
+ - test/list_test.rb