acts_as_list_with_sti_support 0.1.0 → 0.1.1

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/.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: