acts_as_list_with_sti_support 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.specification ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_list_with_sti_support
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Coroutine
13
+ - John Dugan
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-03-10 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ 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.
34
+ email: jdugan@coroutine.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - README.rdoc
41
+ files:
42
+ - MIT-LICENSE
43
+ - README.rdoc
44
+ - Rakefile
45
+ - VERSION
46
+ - init.rb
47
+ - test/test_helper.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/coroutine/acts_as_label_with_sti_support
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.3.6
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Gem version of acts_as_list_with_sti_support Rails plugin, a smarter version of acts_as_list.
78
+ test_files:
79
+ - test/acts_as_list_with_sti_support_test.rb
80
+ - test/test_helper.rb
81
+
data/README.rdoc CHANGED
@@ -38,9 +38,17 @@ The plugin accepts two optional parameters.
38
38
  The +scope+ option can be supplied as a symbol or as a lambda. The generated scope condition
39
39
  will differ depending on the value's Type and the schema of the associated table.
40
40
 
41
- +scope => type_symbol+:: yields "type_id = '99'", if type_id is defined on the table
42
- +scope => type_symbol+:: yields "type = '99'", if type_id is not defined on the table
43
- +scope => lambda { |instance| "type_id = '#{instance.type_id}'"}+:: yields "type_id = '99'"
41
+ # yields "type_id = '99'", if type_id is defined on the table
42
+ # yields "type = '99'", if type_id is not defined on the table
43
+ class MyListItem < ActiveRecord::Base
44
+ acts_as_list :scope => :type
45
+ end
46
+
47
+ # yields "type_id = '99'"
48
+ class MyListItem < ActiveRecord::Base
49
+ acts_as_list :scope => lambda { |instance| "type_id = '#{instance.type_id}'" }
50
+ end
51
+
44
52
 
45
53
  If you use the plugin within a single table inheritance (STI) design, no extra configuration is needed
46
54
  to cause the position values to be scoped by type.
data/Rakefile CHANGED
@@ -32,7 +32,7 @@ begin
32
32
  gemspec.summary = "Gem version of acts_as_list_with_sti_support Rails plugin, a smarter version of acts_as_list."
33
33
  gemspec.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."
34
34
  gemspec.email = "jdugan@coroutine.com"
35
- gemspec.homepage = "http://github.com/coroutine/acts_as_label_with_sti_support"
35
+ gemspec.homepage = "http://github.com/coroutine/acts_as_list_with_sti_support"
36
36
  gemspec.authors = ["Coroutine", "John Dugan"]
37
37
  gemspec.add_dependency "activesupport"
38
38
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
@@ -0,0 +1,54 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{acts_as_list_with_sti_support}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Coroutine", "John Dugan"]
12
+ s.date = %q{2010-03-10}
13
+ 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.}
14
+ s.email = %q{jdugan@coroutine.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".specification",
20
+ "MIT-LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "acts_as_list_with_sti_support-0.1.0.gem",
25
+ "acts_as_list_with_sti_support.gemspec",
26
+ "init.rb",
27
+ "lib/acts_as_list_with_sti_support.rb",
28
+ "test/acts_as_list_with_sti_support_test.rb",
29
+ "test/test_helper.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/coroutine/acts_as_list_with_sti_support}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.6}
35
+ s.summary = %q{Gem version of acts_as_list_with_sti_support Rails plugin, a smarter version of acts_as_list.}
36
+ s.test_files = [
37
+ "test/acts_as_list_with_sti_support_test.rb",
38
+ "test/test_helper.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ s.add_runtime_dependency(%q<activesupport>, [">= 0"])
47
+ else
48
+ s.add_dependency(%q<activesupport>, [">= 0"])
49
+ end
50
+ else
51
+ s.add_dependency(%q<activesupport>, [">= 0"])
52
+ end
53
+ end
54
+
@@ -0,0 +1,319 @@
1
+ module Coroutine #:nodoc:
2
+ module Acts #:nodoc:
3
+ module List #:nodoc:
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+
10
+ module ClassMethods
11
+
12
+ # = Description
13
+ #
14
+ # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
15
+ # The class that has this specified needs to have a +position+ column defined as an integer on
16
+ # the mapped database table.
17
+ #
18
+ #
19
+ # = Usage
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
+ # = Configuration
35
+ #
36
+ # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
37
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
38
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
39
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
40
+ # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
41
+ #
42
+ def acts_as_list(options = {})
43
+
44
+ #-------------------------------------------
45
+ # scrub options
46
+ #-------------------------------------------
47
+ options = {} if !options.is_a?(Hash)
48
+ column = options.key?(:column) ? options[:column] : :position
49
+ scope = options.key?(:scope) ? options[:scope] : "1 = 1"
50
+
51
+
52
+ #--------------------------------------------
53
+ # mix methods into class definition
54
+ #--------------------------------------------
55
+ class_eval do
56
+
57
+ # Add inheritable accessors
58
+ write_inheritable_attribute :acts_as_list_column, column
59
+ class_inheritable_reader :acts_as_list_column
60
+ write_inheritable_attribute :acts_as_list_scope, scope
61
+ class_inheritable_reader :acts_as_list_scope
62
+ write_inheritable_attribute :acts_as_list_default_scope, "1 = 1"
63
+ class_inheritable_reader :acts_as_list_default_scope
64
+ write_inheritable_attribute :acts_as_list_scope_condition, nil
65
+ class_inheritable_reader :acts_as_list_scope_condition
66
+
67
+
68
+ # Add validations (column is allowed to be nil to support soft deletes)
69
+ validates_numericality_of column, :only_integer => true, :greater_than => 0, :allow_nil => true
70
+
71
+
72
+ # Add callbacks
73
+ before_validation_on_create :add_to_list_bottom
74
+ before_destroy :remove_from_list
75
+
76
+
77
+ # if no default scoping, order by position
78
+ if self.default_scoping.empty?
79
+ default_scope :order => column
80
+ end
81
+
82
+
83
+ # Include instance methods
84
+ include Coroutine::Acts::List::InstanceMethods
85
+
86
+ end
87
+
88
+ end
89
+ end
90
+
91
+
92
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
93
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
94
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
95
+ # the first in the list of all chapters.
96
+ #
97
+ module InstanceMethods
98
+
99
+ # Return the instance's class object
100
+ def acts_as_list_class
101
+ self.class
102
+ end
103
+
104
+ # Returns the column name that holds the position value.
105
+ def position_column
106
+ acts_as_list_column
107
+ end
108
+
109
+ # Returns the scope condition appropriate for the specified definition. (This could probably
110
+ # be refactored for brevity.)
111
+ def scope_condition
112
+ if acts_as_list_scope_condition.nil?
113
+
114
+ # if symbol, do convenience conversions
115
+ if acts_as_list_scope.is_a?(Symbol)
116
+ scope_as_sym = acts_as_list_scope
117
+ scope_as_str = acts_as_list_scope.to_s
118
+ if scope_as_str.nil?
119
+ acts_as_list_scope_condition = "#{scope_as_str} IS NULL"
120
+ else
121
+ scope_with_id = scope_as_str + "_id"
122
+ if scope_as_str !~ /_id$/ && acts_as_list_class.column_names.include?("#{scope_with_id}")
123
+ scope_as_sym = scope_with_id.to_sym
124
+ scope_as_str = scope_as_sym.to_s
125
+ end
126
+ acts_as_list_scope_condition = "#{scope_as_str} = \'#{self[scope_as_sym]}\'"
127
+ end
128
+
129
+ # if lambda, execute in scope of instance
130
+ elsif acts_as_list_scope.is_a?(Proc)
131
+ acts_as_list_scope_condition = acts_as_list_scope.call(self)
132
+
133
+ # else, return string as is
134
+ else
135
+ acts_as_list_scope_condition = !acts_as_list_scope.blank? ? acts_as_list_scope.to_s : acts_as_list_default_scope.to_s
136
+ end
137
+ end
138
+
139
+ acts_as_list_scope_condition
140
+ end
141
+
142
+ # Insert the item at the given position (defaults to the top position of 1).
143
+ def insert_at(position = 1)
144
+ insert_at_position(position)
145
+ end
146
+
147
+ # Swap positions with the next lower item, if one exists.
148
+ def move_lower
149
+ return unless lower_item
150
+
151
+ acts_as_list_class.transaction do
152
+ lower_item.decrement_position
153
+ increment_position
154
+ end
155
+ end
156
+
157
+ # Swap positions with the next higher item, if one exists.
158
+ def move_higher
159
+ return unless higher_item
160
+
161
+ acts_as_list_class.transaction do
162
+ higher_item.increment_position
163
+ decrement_position
164
+ end
165
+ end
166
+
167
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
168
+ # position adjusted accordingly.
169
+ def move_to_bottom
170
+ return unless in_list?
171
+ acts_as_list_class.transaction do
172
+ decrement_positions_on_lower_items
173
+ assume_bottom_position
174
+ end
175
+ end
176
+
177
+ # Move to the top of the list. If the item is already in the list, the items above it have their
178
+ # position adjusted accordingly.
179
+ def move_to_top
180
+ return unless in_list?
181
+ acts_as_list_class.transaction do
182
+ increment_positions_on_higher_items
183
+ assume_top_position
184
+ end
185
+ end
186
+
187
+ # Removes the item from the list.
188
+ def remove_from_list
189
+ if in_list?
190
+ decrement_positions_on_lower_items
191
+ update_attribute position_column, nil
192
+ end
193
+ end
194
+
195
+ # Increase the position of this item without adjusting the rest of the list.
196
+ def increment_position
197
+ return unless in_list?
198
+ update_attribute position_column, self.send(position_column).to_i + 1
199
+ end
200
+
201
+ # Decrease the position of this item without adjusting the rest of the list.
202
+ def decrement_position
203
+ return unless in_list?
204
+ update_attribute position_column, self.send(position_column).to_i - 1
205
+ end
206
+
207
+ # Return +true+ if this object is the first in the list.
208
+ def first?
209
+ return false unless in_list?
210
+ self.send(position_column) == 1
211
+ end
212
+
213
+ # Return +true+ if this object is the last in the list.
214
+ def last?
215
+ return false unless in_list?
216
+ self.send(position_column) == bottom_position_in_list
217
+ end
218
+
219
+ # Return the next higher item in the list.
220
+ def higher_item
221
+ return nil unless in_list?
222
+ acts_as_list_class.find(:first, :conditions =>
223
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
224
+ )
225
+ end
226
+
227
+ # Return the next lower item in the list.
228
+ def lower_item
229
+ return nil unless in_list?
230
+ acts_as_list_class.find(:first, :conditions =>
231
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
232
+ )
233
+ end
234
+
235
+ # Test if this record is in a list
236
+ def in_list?
237
+ !send(position_column).nil?
238
+ end
239
+
240
+ private
241
+ def add_to_list_top
242
+ increment_positions_on_all_items
243
+ end
244
+
245
+ def add_to_list_bottom
246
+ self[position_column] = bottom_position_in_list.to_i + 1
247
+ end
248
+
249
+ # Returns the bottom position number in the list.
250
+ # bottom_position_in_list # => 2
251
+ def bottom_position_in_list(except = nil)
252
+ item = bottom_item(except)
253
+ item ? item.send(position_column) : 0
254
+ end
255
+
256
+ # Returns the bottom item
257
+ def bottom_item(except = nil)
258
+ conditions = scope_condition
259
+ conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
260
+ acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
261
+ end
262
+
263
+ # Forces item to assume the bottom position in the list.
264
+ def assume_bottom_position
265
+ update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
266
+ end
267
+
268
+ # Forces item to assume the top position in the list.
269
+ def assume_top_position
270
+ update_attribute(position_column, 1)
271
+ end
272
+
273
+ # This has the effect of moving all the higher items up one.
274
+ def decrement_positions_on_higher_items(position)
275
+ acts_as_list_class.update_all(
276
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
277
+ )
278
+ end
279
+
280
+ # This has the effect of moving all the lower items up one.
281
+ def decrement_positions_on_lower_items
282
+ return unless in_list?
283
+ acts_as_list_class.update_all(
284
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
285
+ )
286
+ end
287
+
288
+ # This has the effect of moving all the higher items down one.
289
+ def increment_positions_on_higher_items
290
+ return unless in_list?
291
+ acts_as_list_class.update_all(
292
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
293
+ )
294
+ end
295
+
296
+ # This has the effect of moving all the lower items down one.
297
+ def increment_positions_on_lower_items(position)
298
+ acts_as_list_class.update_all(
299
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
300
+ )
301
+ end
302
+
303
+ # Increments position (<tt>position_column</tt>) of all items in the list.
304
+ def increment_positions_on_all_items
305
+ acts_as_list_class.update_all(
306
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
307
+ )
308
+ end
309
+
310
+ def insert_at_position(position)
311
+ remove_from_list
312
+ increment_positions_on_lower_items(position)
313
+ self.update_attribute(position_column, position)
314
+ end
315
+
316
+ end
317
+ end
318
+ end
319
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 1
9
+ version: 0.1.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Coroutine
@@ -39,14 +39,19 @@ extensions: []
39
39
  extra_rdoc_files:
40
40
  - README.rdoc
41
41
  files:
42
+ - .specification
42
43
  - MIT-LICENSE
43
44
  - README.rdoc
44
45
  - Rakefile
45
46
  - VERSION
47
+ - acts_as_list_with_sti_support-0.1.0.gem
48
+ - acts_as_list_with_sti_support.gemspec
46
49
  - init.rb
50
+ - lib/acts_as_list_with_sti_support.rb
51
+ - test/acts_as_list_with_sti_support_test.rb
47
52
  - test/test_helper.rb
48
53
  has_rdoc: true
49
- homepage: http://github.com/coroutine/acts_as_label_with_sti_support
54
+ homepage: http://github.com/coroutine/acts_as_list_with_sti_support
50
55
  licenses: []
51
56
 
52
57
  post_install_message: