sortifiable 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/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
1
+ *0.1.1 (February 7th, 2011)
2
+
3
+ * Use the class in which act_as_list is called
4
+
5
+ * Wrap list updates inside a transaction block
6
+
7
+ * Don't include instance methods in all models
8
+
9
+ * Don't add acts_as_list callbacks to all models
10
+
11
+
1
12
  *0.1.0 (February 6th, 2011)
2
13
 
3
- * First release
14
+ * First release
@@ -1,3 +1,3 @@
1
1
  module Sortifiable
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/sortifiable.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'active_support/concern'
2
1
  require 'active_support/core_ext/array/wrap'
3
2
  require 'active_support/core_ext/class/attribute'
4
3
  require 'active_support/core_ext/hash/reverse_merge'
@@ -24,14 +23,12 @@ require 'sortifiable/version'
24
23
  # todo_list.first.move_to_bottom
25
24
  # todo_list.last.move_higher
26
25
  module Sortifiable
27
- extend ActiveSupport::Concern
28
-
29
- included do
30
- class_attribute :acts_as_list_options, :instance_writer => false
31
- self.acts_as_list_options = {}
32
-
33
- before_create :add_to_list_bottom
34
- before_destroy :decrement_position_on_lower_items, :if => :in_list?
26
+ def self.included(base) #:nodoc:
27
+ base.extend(ClassMethods)
28
+ base.class_eval do
29
+ class_attribute :acts_as_list_options, :instance_writer => false
30
+ self.acts_as_list_options = {}
31
+ end
35
32
  end
36
33
 
37
34
  module ClassMethods
@@ -70,6 +67,12 @@ module Sortifiable
70
67
  options[:scope] = "#{options[:scope]}_id".to_sym
71
68
  end
72
69
 
70
+ options[:class] = self
71
+
72
+ include InstanceMethods
73
+ before_create :add_to_list_bottom
74
+ before_destroy :decrement_position_on_lower_items, :if => :in_list?
75
+
73
76
  self.acts_as_list_options = options
74
77
  end
75
78
  end
@@ -79,189 +82,199 @@ module Sortifiable
79
82
  # list, so <tt>chapter.move_lower</tt> would move that chapter lower in the
80
83
  # list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+
81
84
  # if that chapter is the first in the list of all chapters.
82
-
83
- # Add the item to the end of the list
84
- def add_to_list
85
- remove_from_list if in_list?
86
- update_attribute(position_column, last_position + 1)
87
- end
88
-
89
- # Returns the current position
90
- def current_position
91
- send(position_column).to_i
92
- end
93
-
94
- # Decrease the position of this item without adjusting the rest of the list.
95
- def decrement_position
96
- in_list? && update_attribute(position_column, current_position - 1)
97
- end
98
-
99
- # Return +true+ if this object is the first in the list.
100
- def first?
101
- in_list? && current_position == 1
102
- end
103
- alias_method :top?, :first?
104
-
105
- # Returns the first item in the list
106
- def first_item
107
- list_scope.first
108
- end
109
- alias_method :top_item, :first_item
110
-
111
- # Return the next higher item in the list.
112
- def higher_item
113
- item_at_offset(-1)
114
- end
115
- alias_method :previous_item, :higher_item
116
-
117
- # Return items lower than this item or an empty array if it is the last item
118
- def higher_items
119
- list_scope.where(["#{quoted_position_column} < ?", current_position]).all
120
- end
121
-
122
- # Test if this record is in a list
123
- def in_list?
124
- !new_record? && !send(position_column).nil?
125
- end
126
-
127
- # Increase the position of this item without adjusting the rest of the list.
128
- def increment_position
129
- in_list? && update_attribute(position_column, current_position + 1)
130
- end
131
-
132
- # Insert the item at the given position (defaults to the top position of 1).
133
- def insert_at(position = 1)
134
- if position > 0
135
- remove_from_list
136
- if position > last_position
137
- add_to_list
138
- else
139
- increment_position_on_lower_items(position - 1)
140
- update_attribute(position_column, position)
85
+ module InstanceMethods
86
+ # Add the item to the end of the list
87
+ def add_to_list
88
+ list_class.transaction do
89
+ remove_from_list if in_list?
90
+ update_attribute(position_column, last_position + 1)
141
91
  end
142
- else
143
- false
144
92
  end
145
- end
146
-
147
- # Return the item at the offset specified from the current position
148
- def item_at_offset(offset)
149
- in_list? ? offset_scope(offset).first : nil
150
- end
151
-
152
- # Return +true+ if this object is the last in the list.
153
- def last?
154
- in_list? && current_position == last_position
155
- end
156
- alias_method :bottom?, :last?
157
93
 
158
- # Returns the bottom item
159
- def last_item
160
- list_scope.last
161
- end
162
- alias_method :bottom_item, :last_item
94
+ # Returns the current position
95
+ def current_position
96
+ send(position_column).to_i
97
+ end
163
98
 
164
- # Returns the bottom position in the list.
165
- def last_position
166
- item = last_item
167
- item ? item.current_position : 0
168
- end
169
- alias_method :bottom_position, :last_position
99
+ # Decrease the position of this item without adjusting the rest of the list.
100
+ def decrement_position
101
+ in_list? && update_attribute(position_column, current_position - 1)
102
+ end
170
103
 
171
- # Return the next lower item in the list.
172
- def lower_item
173
- item_at_offset(1)
174
- end
175
- alias_method :next_item, :lower_item
104
+ # Return +true+ if this object is the first in the list.
105
+ def first?
106
+ in_list? && current_position == 1
107
+ end
108
+ alias_method :top?, :first?
176
109
 
177
- # Return items lower than this item or an empty array if it is the last item
178
- def lower_items
179
- list_scope.where(["#{quoted_position_column} > ?", current_position]).all
180
- end
110
+ # Returns the first item in the list
111
+ def first_item
112
+ list_scope.first
113
+ end
114
+ alias_method :top_item, :first_item
181
115
 
182
- # Swap positions with the next higher item, if one exists.
183
- def move_higher
184
- in_list? && (first? || insert_at(current_position - 1))
185
- end
186
- alias_method :move_up, :move_higher
116
+ # Return the next higher item in the list.
117
+ def higher_item
118
+ item_at_offset(-1)
119
+ end
120
+ alias_method :previous_item, :higher_item
187
121
 
188
- # Swap positions with the next lower item, if one exists.
189
- def move_lower
190
- in_list? && (last? || insert_at(current_position + 1))
191
- end
192
- alias_method :move_down, :move_lower
122
+ # Return items lower than this item or an empty array if it is the last item
123
+ def higher_items
124
+ list_scope.where(["#{quoted_position_column} < ?", current_position]).all
125
+ end
193
126
 
194
- # Move to the bottom of the list. If the item is already in the list,
195
- # the items below it have their position adjusted accordingly.
196
- def move_to_bottom
197
- in_list? && (last? || add_to_list)
198
- end
127
+ # Test if this record is in a list
128
+ def in_list?
129
+ !new_record? && !send(position_column).nil?
130
+ end
199
131
 
200
- # Move to the top of the list. If the item is already in the list,
201
- # the items above it have their position adjusted accordingly.
202
- def move_to_top
203
- in_list? && (first? || insert_at(1))
204
- end
132
+ # Increase the position of this item without adjusting the rest of the list.
133
+ def increment_position
134
+ in_list? && update_attribute(position_column, current_position + 1)
135
+ end
205
136
 
206
- # Removes the item from the list.
207
- def remove_from_list
208
- if in_list?
209
- decrement_position_on_lower_items
210
- update_attribute(position_column, nil)
211
- else
212
- false
137
+ # Insert the item at the given position (defaults to the top position of 1).
138
+ def insert_at(position = 1)
139
+ if position > 0
140
+ list_class.transaction do
141
+ remove_from_list
142
+ if position > last_position
143
+ add_to_list
144
+ else
145
+ increment_position_on_lower_items(position - 1)
146
+ update_attribute(position_column, position)
147
+ end
148
+ end
149
+ else
150
+ false
151
+ end
213
152
  end
214
- end
215
153
 
216
- private
217
- def add_to_list_bottom #:nodoc:
218
- send("#{position_column}=".to_sym, last_position + 1)
154
+ # Return the item at the offset specified from the current position
155
+ def item_at_offset(offset)
156
+ in_list? ? offset_scope(offset).first : nil
219
157
  end
220
158
 
221
- def base_scope #:nodoc:
222
- self.class.unscoped.where(scope_condition)
159
+ # Return +true+ if this object is the last in the list.
160
+ def last?
161
+ in_list? && current_position == last_position
223
162
  end
163
+ alias_method :bottom?, :last?
224
164
 
225
- def decrement_position_on_lower_items #:nodoc:
226
- lower_scope(current_position).update_all(position_update('- 1'))
165
+ # Returns the bottom item
166
+ def last_item
167
+ list_scope.last
227
168
  end
169
+ alias_method :bottom_item, :last_item
228
170
 
229
- def increment_position_on_lower_items(position) #:nodoc:
230
- lower_scope(position).update_all(position_update('+ 1'))
171
+ # Returns the bottom position in the list.
172
+ def last_position
173
+ item = last_item
174
+ item ? item.current_position : 0
231
175
  end
176
+ alias_method :bottom_position, :last_position
232
177
 
233
- def list_scope #:nodoc:
234
- base_scope.order(position_column).where("#{quoted_position_column} IS NOT NULL")
178
+ # Return the next lower item in the list.
179
+ def lower_item
180
+ item_at_offset(1)
235
181
  end
182
+ alias_method :next_item, :lower_item
236
183
 
237
- def lower_scope(position) #:nodoc:
238
- base_scope.where(["#{quoted_position_column} > ?", position])
184
+ # Return items lower than this item or an empty array if it is the last item
185
+ def lower_items
186
+ list_scope.where(["#{quoted_position_column} > ?", current_position]).all
239
187
  end
240
188
 
241
- def offset_scope(offset) #:nodoc:
242
- base_scope.where(position_column => current_position + offset)
189
+ # Swap positions with the next higher item, if one exists.
190
+ def move_higher
191
+ in_list? && (first? || insert_at(current_position - 1))
243
192
  end
193
+ alias_method :move_up, :move_higher
244
194
 
245
- def position_column #:nodoc:
246
- acts_as_list_options[:column]
195
+ # Swap positions with the next lower item, if one exists.
196
+ def move_lower
197
+ in_list? && (last? || insert_at(current_position + 1))
247
198
  end
199
+ alias_method :move_down, :move_lower
248
200
 
249
- def position_update(direction) #:nodoc:
250
- "#{quoted_position_column} = (#{quoted_position_column} #{direction})"
201
+ # Move to the bottom of the list. If the item is already in the list,
202
+ # the items below it have their position adjusted accordingly.
203
+ def move_to_bottom
204
+ in_list? && (last? || add_to_list)
251
205
  end
252
206
 
253
- def quoted_position_column #:nodoc:
254
- connection.quote_column_name(position_column)
207
+ # Move to the top of the list. If the item is already in the list,
208
+ # the items above it have their position adjusted accordingly.
209
+ def move_to_top
210
+ in_list? && (first? || insert_at(1))
255
211
  end
256
212
 
257
- def scope_condition #:nodoc:
258
- if acts_as_list_options[:scope].is_a?(String)
259
- instance_eval("\"#{acts_as_list_options[:scope]}\"")
213
+ # Removes the item from the list.
214
+ def remove_from_list
215
+ if in_list?
216
+ list_class.transaction do
217
+ decrement_position_on_lower_items
218
+ update_attribute(position_column, nil)
219
+ end
260
220
  else
261
- Array.wrap(acts_as_list_options[:scope]).inject({}){ |m,k| m[k] = send(k); m }
221
+ false
262
222
  end
263
223
  end
264
224
 
225
+ private
226
+ def add_to_list_bottom #:nodoc:
227
+ send("#{position_column}=".to_sym, last_position + 1)
228
+ end
229
+
230
+ def base_scope #:nodoc:
231
+ list_class.unscoped.where(scope_condition)
232
+ end
233
+
234
+ def decrement_position_on_lower_items #:nodoc:
235
+ lower_scope(current_position).update_all(position_update('- 1'))
236
+ end
237
+
238
+ def increment_position_on_lower_items(position) #:nodoc:
239
+ lower_scope(position).update_all(position_update('+ 1'))
240
+ end
241
+
242
+ def list_class #:nodoc:
243
+ acts_as_list_options[:class]
244
+ end
245
+
246
+ def list_scope #:nodoc:
247
+ base_scope.order(position_column).where("#{quoted_position_column} IS NOT NULL")
248
+ end
249
+
250
+ def lower_scope(position) #:nodoc:
251
+ base_scope.where(["#{quoted_position_column} > ?", position])
252
+ end
253
+
254
+ def offset_scope(offset) #:nodoc:
255
+ base_scope.where(position_column => current_position + offset)
256
+ end
257
+
258
+ def position_column #:nodoc:
259
+ acts_as_list_options[:column]
260
+ end
261
+
262
+ def position_update(direction) #:nodoc:
263
+ "#{quoted_position_column} = (#{quoted_position_column} #{direction})"
264
+ end
265
+
266
+ def quoted_position_column #:nodoc:
267
+ connection.quote_column_name(position_column)
268
+ end
269
+
270
+ def scope_condition #:nodoc:
271
+ if acts_as_list_options[:scope].is_a?(String)
272
+ instance_eval("\"#{acts_as_list_options[:scope]}\"")
273
+ else
274
+ Array.wrap(acts_as_list_options[:scope]).inject({}){ |m,k| m[k] = send(k); m }
275
+ end
276
+ end
277
+ end
265
278
  end
266
279
 
267
280
  ActiveRecord::Base.send(:include, Sortifiable)
@@ -10,6 +10,7 @@ def setup_db
10
10
  silence_stream(STDOUT) do
11
11
  ActiveRecord::Schema.define(:version => 1) do
12
12
  create_table :mixins do |t|
13
+ t.column :type, :string
13
14
  t.column :pos, :integer
14
15
  t.column :parent_id, :integer
15
16
  t.column :parent_type, :string
@@ -31,7 +32,7 @@ setup_db
31
32
  class Mixin < ActiveRecord::Base
32
33
  end
33
34
 
34
- class ListMixin < Mixin
35
+ class ListMixin < ActiveRecord::Base
35
36
  acts_as_list :column => "pos", :scope => :parent
36
37
  set_table_name "mixins"
37
38
  default_scope order(:pos)
@@ -46,10 +47,9 @@ end
46
47
  class ListWithStringScopeMixin < ActiveRecord::Base
47
48
  acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
48
49
  set_table_name "mixins"
49
- default_scope order(:pos)
50
50
  end
51
51
 
52
- class ArrayScopeListMixin < Mixin
52
+ class ArrayScopeListMixin < ActiveRecord::Base
53
53
  acts_as_list :column => "pos", :scope => [:parent_id, :parent_type]
54
54
  set_table_name "mixins"
55
55
  default_scope order(:pos)
@@ -57,6 +57,31 @@ end
57
57
 
58
58
  teardown_db
59
59
 
60
+ class NonListTest < Test::Unit::TestCase
61
+
62
+ def setup
63
+ setup_db
64
+ end
65
+
66
+ def teardown
67
+ teardown_db
68
+ end
69
+
70
+ def test_callbacks_are_not_added_to_all_models
71
+ Mixin.create! :pos => 1, :parent_id => 5
72
+ assert_equal 1, Mixin.first.id
73
+
74
+ Mixin.find(1).destroy
75
+ assert_equal [], Mixin.all
76
+ end
77
+
78
+ def test_instance_methods_are_not_included_in_all_models
79
+ Mixin.create! :pos => 1, :parent_id => 5
80
+ assert_equal false, Mixin.first.respond_to?(:in_list?)
81
+ end
82
+
83
+ end
84
+
60
85
  class ListTest < Test::Unit::TestCase
61
86
 
62
87
  def setup
@@ -267,13 +292,23 @@ class ListSubTest < Test::Unit::TestCase
267
292
 
268
293
  def setup
269
294
  setup_db
270
- (1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
295
+ (1..4).each do |i|
296
+ klass = ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2)
297
+ klass.create! :pos => i, :parent_id => 5000
298
+ end
271
299
  end
272
300
 
273
301
  def teardown
274
302
  teardown_db
275
303
  end
276
304
 
305
+ def test_sti_class
306
+ assert_instance_of ListMixinSub1, ListMixin.find(1)
307
+ assert_instance_of ListMixinSub2, ListMixin.find(2)
308
+ assert_instance_of ListMixinSub1, ListMixin.find(3)
309
+ assert_instance_of ListMixinSub2, ListMixin.find(4)
310
+ end
311
+
277
312
  def test_reordering
278
313
  assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:id)
279
314
 
@@ -381,6 +416,10 @@ class ListSubTest < Test::Unit::TestCase
381
416
  assert_equal [], ListMixin.find(4).lower_items.map(&:id)
382
417
  end
383
418
 
419
+ def test_list_class
420
+ assert_equal [1, 2, 3, 4], ListMixin.all.map(&:pos)
421
+ end
422
+
384
423
  end
385
424
 
386
425
  class ArrayScopeListTest < Test::Unit::TestCase
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sortifiable
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 0
10
- version: 0.1.0
9
+ - 1
10
+ version: 0.1.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andrew White