acts_as_list 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gemtest ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in acts_as_list-rails3.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,35 @@
1
+ = ActsAsList
2
+
3
+ == Description
4
+
5
+ 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.
6
+
7
+
8
+ == Example
9
+
10
+ class TodoList < ActiveRecord::Base
11
+ has_many :todo_items, :order => "position"
12
+ end
13
+
14
+ class TodoItem < ActiveRecord::Base
15
+ belongs_to :todo_list
16
+ acts_as_list :scope => :todo_list
17
+ end
18
+
19
+ todo_list.first.move_to_bottom
20
+ todo_list.last.move_higher
21
+
22
+ == Contributing to acts_as_list
23
+
24
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
25
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
26
+ * Fork the project
27
+ * Start a feature/bugfix branch
28
+ * Commit and push until you are happy with your contribution
29
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
30
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
31
+
32
+ == Copyright
33
+
34
+ Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
35
+
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ #require 'rake'
5
+ require 'rake/testtask'
6
+
7
+ # Run the test with 'rake' or 'rake test'
8
+ desc 'Default: run acts_as_list unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the acts_as_list plugin.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib' << 'test'
14
+ t.pattern = 'test/**/test_*.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+
19
+
20
+ # Run the rdoc task to generate rdocs for this gem
21
+ require 'rdoc/task'
22
+ RDoc::Task.new do |rdoc|
23
+ require "acts_as_list/version"
24
+ version = ActiveRecord::Acts::List::VERSION
25
+
26
+ rdoc.rdoc_dir = 'rdoc'
27
+ rdoc.title = "acts_as_list-rails3 #{version}"
28
+ rdoc.rdoc_files.include('README*')
29
+ rdoc.rdoc_files.include('lib/**/*.rb')
30
+ end
31
+
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'acts_as_list/version'
4
+
5
+ Gem::Specification.new do |s|
6
+
7
+ # Description Meta...
8
+ s.name = 'acts_as_list'
9
+ s.version = ActiveRecord::Acts::List::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ['David Heinemeier Hansson', 'Swanand Pagnis', 'Quinn Chaffee']
12
+ s.email = ['swanand.pagnis@gmail.com']
13
+ s.homepage = 'http://github.com/swanandp/acts_as_list'
14
+ s.summary = %q{A gem allowing a active_record model to act_as_list.}
15
+ s.description = %q{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.}
16
+ s.rubyforge_project = 'acts_as_list'
17
+
18
+
19
+ # Load Paths...
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ['lib']
24
+
25
+
26
+ # Dependencies (installed via 'bundle install')...
27
+ s.add_development_dependency("bundler", ["~> 1.0.0"])
28
+ s.add_development_dependency("activerecord", [">= 1.15.4.7794"])
29
+ s.add_development_dependency("rdoc")
30
+ s.add_development_dependency("sqlite3-ruby")
31
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ $:.unshift "#{File.dirname(__FILE__)}/lib"
2
+ require 'acts_as_list'
data/lib/acts_as_list.rb CHANGED
@@ -1,254 +1,2 @@
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
- lower = lower_item
83
- return unless lower
84
- acts_as_list_class.transaction do
85
- self.update_attribute(position_column, lower.send(position_column))
86
- lower.decrement_position
87
- end
88
- end
89
-
90
- # Swap positions with the next higher item, if one exists.
91
- def move_higher
92
- higher = higher_item
93
- return unless higher
94
- acts_as_list_class.transaction do
95
- self.update_attribute(position_column, higher.send(position_column))
96
- higher.increment_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_s}", :order => "#{position_column} DESC"
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_s}", :order => "#{position_column} ASC"
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 }
1
+ require 'acts_as_list/active_record/acts/list'
2
+ ActiveRecord::Base.class_eval { include ActiveRecord::Acts::List }
@@ -0,0 +1,271 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module List #:nodoc:
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
9
+ # The class that has this specified needs to have a +position+ column defined as an integer on
10
+ # the mapped database table.
11
+ #
12
+ # Todo list example:
13
+ #
14
+ # class TodoList < ActiveRecord::Base
15
+ # has_many :todo_items, :order => "position"
16
+ # end
17
+ #
18
+ # class TodoItem < ActiveRecord::Base
19
+ # belongs_to :todo_list
20
+ # acts_as_list :scope => :todo_list
21
+ # end
22
+ #
23
+ # todo_list.first.move_to_bottom
24
+ # todo_list.last.move_higher
25
+ module ClassMethods
26
+ # Configuration options are:
27
+ #
28
+ # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
29
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
30
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
31
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
32
+ # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
33
+ # * +top_of_list+ - defines the integer used for the top of the list. Defaults to 1. Use 0 to make the collection
34
+ # act more line an array in it's indexing.
35
+ def acts_as_list(options = {})
36
+ configuration = { :column => "position", :scope => "1 = 1", :top_of_list => 1}
37
+ configuration.update(options) if options.is_a?(Hash)
38
+
39
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
40
+
41
+ if configuration[:scope].is_a?(Symbol)
42
+ scope_condition_method = %(
43
+ def scope_condition
44
+ self.class.send(:sanitize_sql_hash_for_conditions, { :#{configuration[:scope].to_s} => send(:#{configuration[:scope].to_s}) })
45
+ end
46
+ )
47
+ elsif configuration[:scope].is_a?(Array)
48
+ scope_condition_method = %(
49
+ def scope_condition
50
+ attrs = %w(#{configuration[:scope].join(" ")}).inject({}) do |memo,column|
51
+ memo[column.intern] = send(column.intern); memo
52
+ end
53
+ self.class.send(:sanitize_sql_hash_for_conditions, attrs)
54
+ end
55
+ )
56
+ else
57
+ scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
58
+ end
59
+
60
+ class_eval <<-EOV
61
+ include ActiveRecord::Acts::List::InstanceMethods
62
+
63
+ def acts_as_list_top
64
+ #{configuration[:top_of_list]}.to_i
65
+ end
66
+
67
+ def acts_as_list_class
68
+ ::#{self.name}
69
+ end
70
+
71
+ def position_column
72
+ '#{configuration[:column]}'
73
+ end
74
+
75
+ #{scope_condition_method}
76
+
77
+ before_destroy :decrement_positions_on_lower_items
78
+ before_create :add_to_list_bottom
79
+ EOV
80
+ end
81
+ end
82
+
83
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
84
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
85
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
86
+ # the first in the list of all chapters.
87
+ module InstanceMethods
88
+ # Insert the item at the given position (defaults to the top position of 1).
89
+ def insert_at(position = acts_as_list_top)
90
+ insert_at_position(position)
91
+ end
92
+
93
+ # Swap positions with the next lower item, if one exists.
94
+ def move_lower
95
+ return unless lower_item
96
+
97
+ acts_as_list_class.transaction do
98
+ lower_item.decrement_position
99
+ increment_position
100
+ end
101
+ end
102
+
103
+ # Swap positions with the next higher item, if one exists.
104
+ def move_higher
105
+ return unless higher_item
106
+
107
+ acts_as_list_class.transaction do
108
+ higher_item.increment_position
109
+ decrement_position
110
+ end
111
+ end
112
+
113
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
114
+ # position adjusted accordingly.
115
+ def move_to_bottom
116
+ return unless in_list?
117
+ acts_as_list_class.transaction do
118
+ decrement_positions_on_lower_items
119
+ assume_bottom_position
120
+ end
121
+ end
122
+
123
+ # Move to the top of the list. If the item is already in the list, the items above it have their
124
+ # position adjusted accordingly.
125
+ def move_to_top
126
+ return unless in_list?
127
+ acts_as_list_class.transaction do
128
+ increment_positions_on_higher_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
+ end
140
+
141
+ # Increase the position of this item without adjusting the rest of the list.
142
+ def increment_position
143
+ return unless in_list?
144
+ update_attribute position_column, self.send(position_column).to_i + 1
145
+ end
146
+
147
+ # Decrease the position of this item without adjusting the rest of the list.
148
+ def decrement_position
149
+ return unless in_list?
150
+ update_attribute position_column, self.send(position_column).to_i - 1
151
+ end
152
+
153
+ # Return +true+ if this object is the first in the list.
154
+ def first?
155
+ return false unless in_list?
156
+ self.send(position_column) == acts_as_list_top
157
+ end
158
+
159
+ # Return +true+ if this object is the last in the list.
160
+ def last?
161
+ return false unless in_list?
162
+ self.send(position_column) == bottom_position_in_list
163
+ end
164
+
165
+ # Return the next higher item in the list.
166
+ def higher_item
167
+ return nil unless in_list?
168
+ acts_as_list_class.find(:first, :conditions =>
169
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
170
+ )
171
+ end
172
+
173
+ # Return the next lower item in the list.
174
+ def lower_item
175
+ return nil unless in_list?
176
+ acts_as_list_class.find(:first, :conditions =>
177
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
178
+ )
179
+ end
180
+
181
+ # Test if this record is in a list
182
+ def in_list?
183
+ !send(position_column).nil?
184
+ end
185
+
186
+ private
187
+ def add_to_list_top
188
+ increment_positions_on_all_items
189
+ end
190
+
191
+ def add_to_list_bottom
192
+ if self[position_column].nil?
193
+ self[position_column] = bottom_position_in_list.to_i + 1
194
+ else
195
+ increment_positions_on_lower_items(self[position_column])
196
+ end
197
+ end
198
+
199
+ # Overwrite this method to define the scope of the list changes
200
+ def scope_condition() "1" end
201
+
202
+ # Returns the bottom position number in the list.
203
+ # bottom_position_in_list # => 2
204
+ def bottom_position_in_list(except = nil)
205
+ item = bottom_item(except)
206
+ item ? item.send(position_column) : acts_as_list_top - 1
207
+ end
208
+
209
+ # Returns the bottom item
210
+ def bottom_item(except = nil)
211
+ conditions = scope_condition
212
+ conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
213
+ acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
214
+ end
215
+
216
+ # Forces item to assume the bottom position in the list.
217
+ def assume_bottom_position
218
+ update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
219
+ end
220
+
221
+ # Forces item to assume the top position in the list.
222
+ def assume_top_position
223
+ update_attribute(position_column, acts_as_list_top)
224
+ end
225
+
226
+ # This has the effect of moving all the higher items up one.
227
+ def decrement_positions_on_higher_items(position)
228
+ acts_as_list_class.update_all(
229
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
230
+ )
231
+ end
232
+
233
+ # This has the effect of moving all the lower items up one.
234
+ def decrement_positions_on_lower_items
235
+ return unless in_list?
236
+ acts_as_list_class.update_all(
237
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
238
+ )
239
+ end
240
+
241
+ # This has the effect of moving all the higher items down one.
242
+ def increment_positions_on_higher_items
243
+ return unless in_list?
244
+ acts_as_list_class.update_all(
245
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
246
+ )
247
+ end
248
+
249
+ # This has the effect of moving all the lower items down one.
250
+ def increment_positions_on_lower_items(position)
251
+ acts_as_list_class.update_all(
252
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
253
+ )
254
+ end
255
+
256
+ # Increments position (<tt>position_column</tt>) of all items in the list.
257
+ def increment_positions_on_all_items
258
+ acts_as_list_class.update_all(
259
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
260
+ )
261
+ end
262
+
263
+ def insert_at_position(position)
264
+ remove_from_list
265
+ increment_positions_on_lower_items(position)
266
+ self.update_attribute(position_column, position)
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end