acts_as_list 0.8.2 → 0.9.0

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.
@@ -0,0 +1,50 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module List
4
+ module NoUpdate
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Lets you selectively disable all act_as_list database updates
9
+ # for the duration of a block.
10
+ #
11
+ # ==== Examples
12
+ # ActiveRecord::Acts::List.acts_as_list_no_update do
13
+ # TodoList....
14
+ # end
15
+ #
16
+ # TodoList.acts_as_list_no_update do
17
+ # TodoList....
18
+ # end
19
+ #
20
+ def acts_as_list_no_update(&block)
21
+ NoUpdate.apply_to(self, &block)
22
+ end
23
+ end
24
+
25
+ class << self
26
+ def apply_to(klass)
27
+ klasses.push(klass)
28
+ yield
29
+ ensure
30
+ klasses.pop
31
+ end
32
+
33
+ def applied_to?(klass)
34
+ klasses.any? { |k| k >= klass }
35
+ end
36
+
37
+ private
38
+
39
+ def klasses
40
+ Thread.current[:act_as_list_no_update] ||= []
41
+ end
42
+ end
43
+
44
+ def act_as_list_no_update?
45
+ NoUpdate.applied_to?(self.class)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveRecord::Acts::List::ScopeMethodDefiner #:nodoc:
2
+ extend ActiveSupport::Inflector
3
+
4
+ def self.call(caller_class, scope)
5
+ scope = idify(scope) if scope.is_a?(Symbol)
6
+
7
+ caller_class.class_eval do
8
+ define_method :scope_name do
9
+ scope
10
+ end
11
+
12
+ if scope.is_a?(Symbol)
13
+ define_method :scope_condition do
14
+ { scope => send(:"#{scope}") }
15
+ end
16
+
17
+ define_method :scope_changed? do
18
+ changed.include?(scope_name.to_s)
19
+ end
20
+ elsif scope.is_a?(Array)
21
+ define_method :scope_condition do
22
+ scope.inject({}) do |hash, column|
23
+ hash.merge!({ column.to_sym => read_attribute(column.to_sym) })
24
+ end
25
+ end
26
+
27
+ define_method :scope_changed? do
28
+ (scope_condition.keys & changed.map(&:to_sym)).any?
29
+ end
30
+ else
31
+ define_method :scope_condition do
32
+ eval "%{#{scope}}"
33
+ end
34
+
35
+ define_method :scope_changed? do
36
+ false
37
+ end
38
+ end
39
+
40
+ self.scope :in_list, lambda { where("#{quoted_position_column_with_table_name} IS NOT NULL") }
41
+ end
42
+ end
43
+
44
+ def self.idify(name)
45
+ return name if name.to_s =~ /_id$/
46
+
47
+ foreign_key(name).to_sym
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveRecord::Acts::List::SequentialUpdatesMethodDefiner #:nodoc:
2
+ def self.call(caller_class, column, sequential_updates_option)
3
+ caller_class.class_eval do
4
+ define_method :sequential_updates? do
5
+ if !defined?(@sequential_updates)
6
+ if sequential_updates_option.nil?
7
+ table_exists = caller_class.connection.table_exists?(caller_class.table_name)
8
+ index_exists = caller_class.connection.index_exists?(caller_class.table_name, column, unique: true)
9
+ @sequential_updates = table_exists && index_exists
10
+ else
11
+ @sequential_updates = sequential_updates_option
12
+ end
13
+ else
14
+ @sequential_updates
15
+ end
16
+ end
17
+
18
+ private :sequential_updates?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveRecord::Acts::List::TopOfListMethodDefiner #:nodoc:
2
+ def self.call(caller_class, top_of_list)
3
+ caller_class.class_eval do
4
+ define_singleton_method :acts_as_list_top do
5
+ top_of_list.to_i
6
+ end
7
+
8
+ define_method :acts_as_list_top do
9
+ top_of_list.to_i
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Acts
3
3
  module List
4
- VERSION = '0.8.2'
4
+ VERSION = '0.9.0'
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,16 @@
1
+ sqlite:
2
+ adapter: sqlite3
3
+ database: "file:memdb1?mode=memory&cache=shared"
4
+
5
+ mysql:
6
+ adapter: mysql2
7
+ username: root
8
+ password:
9
+ database: acts_as_list
10
+
11
+ postgresql:
12
+ adapter: postgresql
13
+ username: postgres
14
+ password:
15
+ database: acts_as_list
16
+ min_messages: ERROR
@@ -1,3 +1,5 @@
1
+ # $DEBUG = true
2
+
1
3
  require "rubygems"
2
4
  require "bundler/setup"
3
5
  begin
@@ -19,3 +21,13 @@ if defined?(ActiveRecord::VERSION) &&
19
21
  end
20
22
 
21
23
  require "shared"
24
+
25
+ # ActiveRecord::Base.logger = Logger.new(STDOUT)
26
+
27
+ def assert_equal_or_nil(a, b)
28
+ if a.nil?
29
+ assert_nil b
30
+ else
31
+ assert_equal a, b
32
+ end
33
+ end
@@ -25,7 +25,7 @@ module Shared
25
25
 
26
26
  ArrayScopeListMixin.where(id: 4).first.move_to_top
27
27
  assert_equal [4, 1, 3, 2], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id)
28
-
28
+
29
29
  ArrayScopeListMixin.where(id: 4).first.insert_at(4)
30
30
  assert_equal [1, 3, 2, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id)
31
31
  assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:pos)
@@ -60,6 +60,11 @@ module Shared
60
60
  assert !new.first?
61
61
  assert new.last?
62
62
 
63
+ new = ArrayScopeListMixin.acts_as_list_no_update { ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') }
64
+ assert_equal_or_nil $default_position,new.pos
65
+ assert_equal $default_position.is_a?(Fixnum), new.first?
66
+ assert !new.last?
67
+
63
68
  new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass')
64
69
  assert_equal 3, new.pos
65
70
  assert !new.first?
@@ -81,6 +86,9 @@ module Shared
81
86
  new = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass')
82
87
  assert_equal 3, new.pos
83
88
 
89
+ new_noup = ArrayScopeListMixin.acts_as_list_no_update { ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass') }
90
+ assert_equal_or_nil $default_position,new_noup.pos
91
+
84
92
  new4 = ArrayScopeListMixin.create(parent_id: 20, parent_type: 'ParentClass')
85
93
  assert_equal 4, new4.pos
86
94
 
@@ -104,6 +112,9 @@ module Shared
104
112
 
105
113
  new4.reload
106
114
  assert_equal 5, new4.pos
115
+
116
+ new_noup.reload
117
+ assert_equal_or_nil $default_position, new_noup.pos
107
118
  end
108
119
 
109
120
  def test_delete_middle
@@ -123,6 +134,12 @@ module Shared
123
134
 
124
135
  assert_equal 1, ArrayScopeListMixin.where(id: 3).first.pos
125
136
  assert_equal 2, ArrayScopeListMixin.where(id: 4).first.pos
137
+
138
+ ArrayScopeListMixin.acts_as_list_no_update { ArrayScopeListMixin.where(id: 3).first.destroy }
139
+
140
+ assert_equal [4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id)
141
+
142
+ assert_equal 2, ArrayScopeListMixin.where(id: 4).first.pos
126
143
  end
127
144
 
128
145
  def test_remove_from_list_should_then_fail_in_list?
@@ -136,10 +153,8 @@ module Shared
136
153
 
137
154
  ArrayScopeListMixin.where(id: 2).first.remove_from_list
138
155
 
139
- assert_equal [2, 1, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id)
140
-
141
156
  assert_equal 1, ArrayScopeListMixin.where(id: 1).first.pos
142
- assert_equal nil, ArrayScopeListMixin.where(id: 2).first.pos
157
+ assert_nil ArrayScopeListMixin.where(id: 2).first.pos
143
158
  assert_equal 2, ArrayScopeListMixin.where(id: 3).first.pos
144
159
  assert_equal 3, ArrayScopeListMixin.where(id: 4).first.pos
145
160
  end
@@ -60,6 +60,11 @@ module Shared
60
60
  assert !new.first?
61
61
  assert new.last?
62
62
 
63
+ new = ListMixin.acts_as_list_no_update { ListMixin.create(parent_id: 20) }
64
+ assert_equal_or_nil $default_position, new.pos
65
+ assert_equal $default_position.is_a?(Fixnum), new.first?
66
+ assert !new.last?
67
+
63
68
  new = ListMixin.create(parent_id: 20)
64
69
  assert_equal 3, new.pos
65
70
  assert !new.first?
@@ -81,6 +86,9 @@ module Shared
81
86
  new = ListMixin.create(parent_id: 20)
82
87
  assert_equal 3, new.pos
83
88
 
89
+ new_noup = ListMixin.acts_as_list_no_update { ListMixin.create(parent_id: 20) }
90
+ assert_equal_or_nil $default_position, new_noup.pos
91
+
84
92
  new4 = ListMixin.create(parent_id: 20)
85
93
  assert_equal 4, new4.pos
86
94
 
@@ -105,18 +113,20 @@ module Shared
105
113
  new4.reload
106
114
  assert_equal 5, new4.pos
107
115
 
108
- last1 = ListMixin.order('pos').last
109
- last2 = ListMixin.order('pos').last
116
+ new_noup.reload
117
+ assert_equal_or_nil $default_position, new_noup.pos
118
+
119
+ last1 = ListMixin.where('pos IS NOT NULL').order('pos').last
120
+ last2 = ListMixin.where('pos IS NOT NULL').order('pos').last
110
121
  last1.insert_at(1)
111
122
  last2.insert_at(1)
112
- assert_equal [1, 2, 3, 4, 5], ListMixin.where(parent_id: 20).order('pos').map(&:pos)
123
+ pos_list = ListMixin.where(parent_id: 20).order("pos ASC#{' NULLS FIRST' if ENV['DB'] == 'postgresql'}").map(&:pos)
124
+ assert_equal [$default_position, 1, 2, 3, 4, 5], pos_list
113
125
  end
114
126
 
115
127
  def test_delete_middle
116
128
  assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
117
129
 
118
-
119
-
120
130
  ListMixin.where(id: 2).first.destroy
121
131
 
122
132
  assert_equal [1, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
@@ -131,6 +141,12 @@ module Shared
131
141
 
132
142
  assert_equal 1, ListMixin.where(id: 3).first.pos
133
143
  assert_equal 2, ListMixin.where(id: 4).first.pos
144
+
145
+ ListMixin.acts_as_list_no_update { ListMixin.where(id: 3).first.destroy }
146
+
147
+ assert_equal [4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
148
+
149
+ assert_equal 2, ListMixin.where(id: 4).first.pos
134
150
  end
135
151
 
136
152
  def test_with_string_based_scope
@@ -175,10 +191,8 @@ module Shared
175
191
 
176
192
  ListMixin.where(id: 2).first.remove_from_list
177
193
 
178
- assert_equal [2, 1, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
179
-
180
194
  assert_equal 1, ListMixin.where(id: 1).first.pos
181
- assert_equal nil, ListMixin.where(id: 2).first.pos
195
+ assert_nil ListMixin.where(id: 2).first.pos
182
196
  assert_equal 2, ListMixin.where(id: 3).first.pos
183
197
  assert_equal 3, ListMixin.where(id: 4).first.pos
184
198
  end
@@ -243,14 +257,24 @@ module Shared
243
257
 
244
258
  assert_equal [5, 1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
245
259
 
260
+ new6 = ListMixin.new(parent_id: 5)
261
+ new6.pos = 3
262
+ new6.save!
263
+ assert_equal 3, new6.pos
264
+ assert !new6.first?
265
+ assert !new6.last?
266
+
267
+ assert_equal [5, 1, 6, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
268
+
246
269
  new = ListMixin.new(parent_id: 5)
247
270
  new.pos = 3
248
- new.save!
271
+ ListMixin.acts_as_list_no_update { new.save! }
249
272
  assert_equal 3, new.pos
273
+ assert_equal 3, new6.pos
250
274
  assert !new.first?
251
275
  assert !new.last?
252
276
 
253
- assert_equal [5, 1, 6, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
277
+ assert_equal [5, 1, 6, 7, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos, id').map(&:id)
254
278
  end
255
279
 
256
280
  def test_non_persisted_records_dont_get_lock_called
@@ -49,12 +49,16 @@ module Shared
49
49
  end
50
50
 
51
51
  assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id)
52
+ assert_equal [5, 10, 15, 20], ListMixin.where(parent_id: 5000).order('id').map(&:pos)
52
53
 
53
54
  ListMixin.where(id: 2).first.move_lower
54
55
  assert_equal [1, 3, 2, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id)
56
+ assert_equal [5, 15, 10, 20], ListMixin.where(parent_id: 5000).order('id').map(&:pos)
57
+
55
58
 
56
59
  ListMixin.where(id: 2).first.move_higher
57
60
  assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5000).order('pos').map(&:id)
61
+ assert_equal [5, 10, 15, 20], ListMixin.where(parent_id: 5000).order('id').map(&:pos)
58
62
 
59
63
  ListMixin.where(id: 1).first.move_to_bottom
60
64
  assert_equal [2, 3, 4, 1], ListMixin.where(parent_id: 5000).order('pos').map(&:id)
@@ -93,7 +97,7 @@ module Shared
93
97
 
94
98
  li3.update_column(:pos, 2) # Make the same position as li2
95
99
 
96
- assert_equal [1, 2, 2, 4], ListMixin.pluck(:pos)
100
+ assert_equal [1, 2, 2, 4], ListMixin.order(:pos).pluck(:pos)
97
101
 
98
102
  assert_equal [li3, li4], li2.lower_items
99
103
  assert_equal [li2, li4], li3.lower_items
@@ -118,6 +122,9 @@ module Shared
118
122
  new = ListMixinSub1.create("parent_id" => 20)
119
123
  assert_equal 3, new.pos
120
124
 
125
+ new_noup = ListMixinSub1.acts_as_list_no_update { ListMixinSub1.create("parent_id" => 20) }
126
+ assert_equal_or_nil $default_position, new_noup.pos
127
+
121
128
  new4 = ListMixin.create("parent_id" => 20)
122
129
  assert_equal 4, new4.pos
123
130
 
@@ -141,6 +148,9 @@ module Shared
141
148
 
142
149
  new4.reload
143
150
  assert_equal 5, new4.pos
151
+
152
+ new_noup.reload
153
+ assert_equal_or_nil $default_position, new_noup.pos
144
154
  end
145
155
 
146
156
  def test_delete_middle
@@ -160,6 +170,12 @@ module Shared
160
170
 
161
171
  assert_equal 1, ListMixin.where(id: 3).first.pos
162
172
  assert_equal 2, ListMixin.where(id: 4).first.pos
173
+
174
+ ListMixin.acts_as_list_no_update { ListMixin.where(id: 3).first.destroy }
175
+
176
+ assert_equal [4], ListMixin.where(parent_id: 5000).order('pos').map(&:id)
177
+
178
+ assert_equal 2, ListMixin.where(id: 4).first.pos
163
179
  end
164
180
 
165
181
  def test_acts_as_list_class
@@ -6,11 +6,11 @@ module Shared
6
6
 
7
7
  def test_insert
8
8
  new = NoAdditionMixin.create(parent_id: 20)
9
- assert_equal nil, new.pos
9
+ assert_nil new.pos
10
10
  assert !new.in_list?
11
11
 
12
12
  new = NoAdditionMixin.create(parent_id: 20)
13
- assert_equal nil, new.pos
13
+ assert_nil new.pos
14
14
  end
15
15
 
16
16
  def test_update_does_not_add_to_list
@@ -46,9 +46,14 @@ module Shared
46
46
  assert new.first?
47
47
  assert !new.last?
48
48
 
49
+ new = TopAdditionMixin.acts_as_list_no_update { TopAdditionMixin.create(parent_id: 20) }
50
+ assert_equal_or_nil $default_position, new.pos
51
+ assert_equal $default_position.is_a?(Fixnum), new.first?
52
+ assert !new.last?
53
+
49
54
  new = TopAdditionMixin.create(parent_id: 20)
50
55
  assert_equal 1, new.pos
51
- assert new.first?
56
+ assert_equal $default_position.nil?, new.first?
52
57
  assert !new.last?
53
58
 
54
59
  new = TopAdditionMixin.create(parent_id: 0)
@@ -67,6 +72,9 @@ module Shared
67
72
  new = TopAdditionMixin.create(parent_id: 20)
68
73
  assert_equal 1, new.pos
69
74
 
75
+ new = TopAdditionMixin.acts_as_list_no_update { TopAdditionMixin.create(parent_id: 20) }
76
+ assert_equal_or_nil $default_position, new.pos
77
+
70
78
  new4 = TopAdditionMixin.create(parent_id: 20)
71
79
  assert_equal 1, new4.pos
72
80
 
@@ -87,6 +95,13 @@ module Shared
87
95
  assert_equal 1, TopAdditionMixin.where(id: 1).first.pos
88
96
  assert_equal 2, TopAdditionMixin.where(id: 3).first.pos
89
97
  assert_equal 3, TopAdditionMixin.where(id: 4).first.pos
98
+
99
+ TopAdditionMixin.acts_as_list_no_update { TopAdditionMixin.where(id: 3).first.destroy }
100
+
101
+ assert_equal [1, 4], TopAdditionMixin.where(parent_id: 5).order('pos').map(&:id)
102
+
103
+ assert_equal 1, TopAdditionMixin.where(id: 1).first.pos
104
+ assert_equal 3, TopAdditionMixin.where(id: 4).first.pos
90
105
  end
91
106
 
92
107
  end