acts_as_list_ar 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.
@@ -0,0 +1,4 @@
1
+ README.rdoc
2
+ MIT-LICENSE
3
+ lib/**/*.rb
4
+ test/**/*.rb
@@ -0,0 +1,3 @@
1
+ pkg
2
+ doc
3
+ .yardoc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 David Heinemeier Hansson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ This gem has been assembled using various different commits from different forks.
2
+ The goal has been to collect maximum functionality, while maintaining support for both Rails 2 and 3 and Ruby 1.8.x and 1.9.x.
3
+ This gem has not undergone testing yet. Please feel free to test it out, improve it, fix it etc.
4
+
5
+ Note: There is also a Mongo Mapper version of acts_as_list out there.
6
+ This is one reason why this gem is postfixed with '_ar' and the module name is ActsAsListAR.
7
+ I hope there will be ports to other storage systems in the future!
8
+
9
+ ---
10
+ Tests:
11
+
12
+ When I run the acts_as_list_test I currently get a few errors for 'insert' and 'insert_at'. The position of a created item is 5, 6 and so on when it should have been
13
+ 1, 2, ...
14
+
15
+ I'm not sure if this is intended or not, i.e if the tests are to be changed or the code. Feel free to respond to this!
16
+ I will try to contact some of the fork members about this issue.
@@ -0,0 +1,64 @@
1
+ = Acts As List
2
+
3
+ This acts_as extension provides the capabilities for sorting and reordering a
4
+ number of objects in a list. The class that has this specified needs to have a
5
+ +position+ column defined as an integer on the mapped database table.
6
+
7
+
8
+ == Install
9
+
10
+ gem install acts_as_list_ar
11
+
12
+ === Rails 3
13
+
14
+ Specify it in your Rails Gemfile:
15
+
16
+ gem "acts_as_list_ar"
17
+
18
+ Then install it:
19
+
20
+ bundle install
21
+
22
+ === Rails 2
23
+
24
+ Specify the gem in your config/environment.rb file:
25
+
26
+ config.gem "acts_as_list_ar"
27
+
28
+ Then install it:
29
+
30
+ $ rake gems:install
31
+
32
+
33
+ == Example
34
+
35
+ class TodoList < ActiveRecord::Base
36
+ has_many :todo_items, :order => "position"
37
+ end
38
+
39
+ class TodoItem < ActiveRecord::Base
40
+ belongs_to :todo_list
41
+ acts_as_list :scope => :todo_list
42
+ end
43
+
44
+ todo_list.first.move_to_bottom
45
+ todo_list.last.move_higher
46
+
47
+
48
+ == Bugs and Feedback
49
+
50
+ If you discover any bugs or want to drop a line, feel free to create an issue on GitHub:
51
+ http://github.com/rails/acts_as_list/issues
52
+
53
+ Alternatively send me a message at:
54
+
55
+
56
+ === Note on Patches/Pull Requests
57
+
58
+ * Fork the project.
59
+ * Make your feature addition or bug fix.
60
+ * Add tests for it. This is important so I don't break it in a
61
+ future version unintentionally.
62
+ * Commit, do not mess with rakefile, version, or history.
63
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
64
+ * Send me a pull request. Bonus points for topic branches.
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "acts_as_list_ar"
8
+ gem.summary = %Q{Gem version of acts_as_list for Active Record with Rails 2 and 3 support}
9
+ gem.description = %Q{Make your model acts as a list. This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list.
10
+ The class that has this specified needs to have a +position+ column defined as an integer on the mapped database table.}
11
+ gem.email = "kmandrup@gmail.com"
12
+ gem.homepage = "http://github.com/rails/acts_as_list"
13
+ gem.authors = ["Kristian Mandrup", "Others"]
14
+ gem.add_dependency "activerecord", ">= 1.15.4.7794"
15
+ # gem.add_development_dependency "yard"
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ task :default => :test
45
+
46
+ # require 'yard'
47
+ # YARD::Rake::YardocTask.new do |t|
48
+ # version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+ # t.options += ['--title', "acts_as_list #{version} Documentation"]
50
+ # end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acts_as_list_ar'
@@ -0,0 +1,326 @@
1
+ # from http://github.com/goncalossilva/acts_as_list/
2
+ module ActsAsListAR
3
+ def scope_name
4
+ "named_scope"
5
+ end
6
+
7
+ def acts_as_list(options = {})
8
+ raise ArgumentError, "Hash expected, got #{options.class.name}" if !options.is_a?(Hash) && !options.empty?
9
+ configuration = { :column => "position", :scope => "'deleted_at IS NULL OR deleted_at IS NOT NULL'" }
10
+ configuration.update(options) if options.is_a?(Hash)
11
+
12
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
13
+
14
+ if configuration[:scope].is_a?(Symbol)
15
+ scope_condition_method = %(
16
+ def scope_condition
17
+ if #{configuration[:scope].to_s}.nil?
18
+ "#{configuration[:scope].to_s} IS NULL"
19
+ else
20
+ "#{configuration[:scope].to_s} = #{configuration[:scope].to_s}"
21
+ end
22
+ end
23
+ )
24
+ else
25
+ scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
26
+ end
27
+
28
+ class_eval <<-EOV
29
+ def self.with_acts_as_list_scope(scope_conditions)
30
+ clazz = self # not sure this works!
31
+ with_scope :find => {:conditions => scope_conditions} do
32
+ if block
33
+ block.arity < 1 ? clazz.instance_eval(&block) : block.call(clazz)
34
+ end
35
+ end
36
+ end
37
+
38
+ def acts_as_list_class
39
+ self.class
40
+ end
41
+
42
+ def position_column
43
+ '#{configuration[:column]}'
44
+ end
45
+
46
+ #{scope_condition_method}
47
+
48
+ before_destroy :eliminate_current_position
49
+ before_create :add_to_list_bottom_when_necessary
50
+ EOV
51
+
52
+ include InstanceMethods
53
+ end
54
+
55
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
56
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
57
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
58
+ # the first in the list of all chapters.
59
+ module InstanceMethods
60
+ # Insert the item at the given position (defaults to the top position of 1).
61
+ def insert_at(position = 1)
62
+ insert_at_position(position)
63
+ end
64
+
65
+ # Inserts the item at the bottom of the list
66
+ def insert_at_bottom
67
+ assume_bottom_position
68
+ end
69
+
70
+ def insert_into(list, options = {})
71
+ position = options.delete(:position) || list.size
72
+ options.each { |attr, value| self.send "#{attr}=", value }
73
+
74
+ list.insert(position, self)
75
+ list.each_with_index { |item, index| item.update_attribute(:position, index + 1) }
76
+ end
77
+
78
+ def move_to(position = 1)
79
+ move_to_position(position)
80
+ end
81
+
82
+ def remove_from(list, options = {})
83
+ if in_list?
84
+ decrement_positions_on_lower_items
85
+
86
+ self.send "#{position_column}=", nil
87
+ options.each { |attr, value| self.send "#{attr}=", value }
88
+ list.delete self
89
+ end
90
+ end
91
+
92
+ # Swap positions with the next lower item, if one exists.
93
+ def move_lower
94
+ lower = lower_item
95
+ return unless lower
96
+ acts_as_list_class.transaction do
97
+ self.update_attribute(position_column, lower.send(position_column))
98
+ lower.decrement_position
99
+ end
100
+ end
101
+
102
+ # Swap positions with the next higher item, if one exists.
103
+ def move_higher
104
+ higher = higher_item
105
+ return unless higher
106
+ acts_as_list_class.transaction do
107
+ self.update_attribute(position_column, higher.send(position_column))
108
+ higher.increment_position
109
+ end
110
+ end
111
+
112
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
113
+ # position adjusted accordingly.
114
+ def move_to_bottom
115
+ # return unless in_list?
116
+ acts_as_list_class.transaction do
117
+ decrement_positions_on_lower_items if in_list?
118
+ assume_bottom_position
119
+ end
120
+ end
121
+
122
+ # Move to the top of the list. If the item is already in the list, the items above it have their
123
+ # position adjusted accordingly.
124
+ def move_to_top
125
+ # return unless in_list?
126
+ acts_as_list_class.transaction do
127
+ # increment_positions_on_higher_items
128
+ in_list? ? increment_positions_on_higher_items : increment_positions_on_all_items
129
+ assume_top_position
130
+ end
131
+ end
132
+
133
+ # Removes the item from the list.
134
+ def remove_from_list
135
+ # if in_list?
136
+ # decrement_positions_on_lower_items
137
+ # update_attribute position_column, nil
138
+ # end
139
+ return unless in_list?
140
+ decrement_positions_on_lower_items
141
+ update_attribute position_column, nil
142
+ end
143
+
144
+ # Increase the position of this item without adjusting the rest of the list.
145
+ def increment_position
146
+ # return unless in_list?
147
+ update_attribute position_column, self.send(position_column).to_i + 1
148
+ end
149
+
150
+ # Decrease the position of this item without adjusting the rest of the list.
151
+ def decrement_position
152
+ # return unless in_list?
153
+ update_attribute position_column, self.send(position_column).to_i - 1
154
+ end
155
+
156
+ # Return +true+ if this object is the first in the list.
157
+ def first?
158
+ # return false unless in_list?
159
+ self.send(position_column) == 1
160
+ end
161
+
162
+ # Return +true+ if this object is the last in the list.
163
+ def last?
164
+ # return false unless in_list?
165
+ self.send(position_column) == bottom_position_in_list
166
+ end
167
+
168
+ # Return the next higher item in the list.
169
+ def higher_item
170
+ # return nil unless in_list? # http://github.com/brightspark3/acts_as_list/commit/8e55352aaa437d23a1ebdeabd5276c6dd5aad6a1
171
+ acts_as_list_class.find(:first, :conditions =>
172
+ "#{scope_condition} AND #{position_column} < #{send(position_column).to_s}", :order => "#{position_column} DESC"
173
+ )
174
+ end
175
+
176
+ # Return the next lower item in the list.
177
+ def lower_item
178
+ # return nil unless in_list?
179
+ acts_as_list_class.find(:first, :conditions =>
180
+ "#{scope_condition} AND #{position_column} > #{send(position_column).to_s}", :order => "#{position_column} ASC"
181
+ )
182
+ end
183
+
184
+ # Test if this record is in a list
185
+ def in_list?
186
+ !send(position_column).nil?
187
+ end
188
+
189
+ private
190
+ def add_to_list_top
191
+ increment_positions_on_all_items
192
+ end
193
+
194
+ def add_to_list_bottom
195
+ self[position_column] = bottom_position_in_list.to_i + 1
196
+ end
197
+
198
+ def add_to_list_bottom_when_necessary
199
+ self[position_column] = bottom_position_in_list.to_i + 1 if send(position_column).nil?
200
+ end
201
+
202
+ # Overwrite this method to define the scope of the list changes
203
+ def scope_condition() "1" end
204
+
205
+ # Returns the bottom position number in the list.
206
+ # bottom_position_in_list # => 2
207
+ def bottom_position_in_list(except = nil)
208
+ item = bottom_item(except)
209
+ item ? item.send(position_column) : 0
210
+ end
211
+
212
+ # Returns the bottom item
213
+ def bottom_item(except = nil)
214
+ conditions = scope_condition
215
+ conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
216
+ acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
217
+ end
218
+
219
+ # Forces item to assume the bottom position in the list.
220
+ def assume_bottom_position
221
+ update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
222
+ end
223
+
224
+ # Forces item to assume the top position in the list.
225
+ def assume_top_position
226
+ update_attribute(position_column, 1)
227
+ end
228
+
229
+ # This has the effect of moving all the higher items up one.
230
+ def decrement_positions_on_higher_items(position)
231
+ acts_as_list_class.update_all("#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}")
232
+ # acts_as_list_class.with_acts_as_list_scope(scope_condition) do
233
+ # update_all("#{position_column} = (#{position_column} - 1)", "#{position_column} <= #{position}")
234
+ # end
235
+ end
236
+
237
+ # This has the effect of moving all the lower items up one.
238
+ def decrement_positions_on_lower_items
239
+ return unless in_list?
240
+ acts_as_list_class.update_all("#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}")
241
+ # acts_as_list_class.with_acts_as_list_scope(scope_condition) do
242
+ # update_all("#{position_column} = (#{position_column} - 1)", "#{position_column} > #{send(position_column).to_i}")
243
+ # end
244
+ end
245
+
246
+ # This has the effect of moving all the higher items down one.
247
+ def increment_positions_on_higher_items
248
+ return unless in_list?
249
+ acts_as_list_class.update_all("#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}")
250
+ # acts_as_list_class.with_acts_as_list_scope(scope_condition) do
251
+ # update_all("#{position_column} = (#{position_column} + 1)", "#{position_column} < #{send(position_column).to_i}")
252
+ # end
253
+ end
254
+
255
+ # This has the effect of moving all the lower items down one.
256
+ def increment_positions_on_lower_items(position)
257
+ acts_as_list_class.update_all("#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}")
258
+ # acts_as_list_class.with_acts_as_list_scope(scope_condition) do
259
+ # update_all("#{position_column} = (#{position_column} + 1)", "#{position_column} >= #{position}")
260
+ # end
261
+ end
262
+
263
+ # Increments position (<tt>position_column</tt>) of all items in the list.
264
+ def increment_positions_on_all_items
265
+ acts_as_list_class.update_all("#{position_column} = (#{position_column} + 1)", "#{scope_condition}")
266
+ # acts_as_list_class.with_acts_as_list_scope(scope_condition) do
267
+ # update_all("#{position_column} = (#{position_column} + 1)")
268
+ # end
269
+ end
270
+
271
+ def insert_at_position(position)
272
+ remove_from_list
273
+ increment_positions_on_lower_items(position)
274
+ self.update_attribute(position_column, position)
275
+ end
276
+
277
+
278
+ # This has the effect of moving all items between two positions (inclusive) up one.
279
+ def decrement_positions_between(low, high)
280
+ acts_as_list_class.update_all(
281
+ "#{position_column} = (#{position_column} - 1)", ["#{scope_condition} AND #{position_column} >= ? AND #{position_column} <= ?", low, high]
282
+ )
283
+ end
284
+
285
+ # This has the effect of moving all items between two positions (inclusive) down one.
286
+ def increment_positions_between(low, high)
287
+ acts_as_list_class.update_all(
288
+ "#{position_column} = (#{position_column} + 1)", ["#{scope_condition} AND #{position_column} >= ? AND #{position_column} <= ?", low, high]
289
+ )
290
+ end
291
+
292
+ # Moves an existing list element to the "new_position" slot.
293
+ def move_to_position(new_position)
294
+ old_position = self.send(position_column)
295
+ unless new_position == old_position
296
+ if new_position < old_position
297
+ # Moving higher in the list (up)
298
+ new_position = [1, new_position].max
299
+ increment_positions_between(new_position, old_position - 1)
300
+ else
301
+ # Moving lower in the list (down)
302
+ new_position = [bottom_position_in_list(self).to_i, new_position].min
303
+ decrement_positions_between(old_position + 1, new_position)
304
+ end
305
+ self.update_attribute(position_column, new_position)
306
+ end
307
+ end
308
+
309
+ def eliminate_current_position
310
+ decrement_positions_on_lower_items if in_list?
311
+ end
312
+ end
313
+ end
314
+
315
+ # Use appropriate ActiveRecord methods
316
+ if not defined?(Rails) or Rails::VERSION::MAJOR == 2
317
+ require 'acts_as_list_ar/rails2'
318
+ elsif Rails::VERSION::MAJOR == 3
319
+ require 'acts_as_list_ar/rails3'
320
+ else
321
+ raise Exception, "Rails 2.x or Rails 3.x expected, got Rails #{Rails::VERSION::MAJOR}.x"
322
+ end
323
+
324
+ # Extend ActiveRecord's functionality
325
+ ActiveRecord::Base.extend ActsAsListAR
326
+