acts_as_list_ar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+