acts_as_list 0.7.4 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ODI0OTU2NzgzMmY0MmI3YjMyMDk2ZDg5YWJjNjkxZDE4ZjViODliYQ==
5
- data.tar.gz: !binary |-
6
- OThlMWNiYjU3MGRmMGQzNjA5NzQ1MDk1MzdiYTg1MGFjODJlNDJmYQ==
2
+ SHA1:
3
+ metadata.gz: d62dacdeb7972152a2ee04cee3cccb8ef26898d7
4
+ data.tar.gz: fe6b02bfa1c5ed33407d1852c7b988a1848c7cd4
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZDMzOWRkNzQ2ZjNmZGRlM2QxYzJkN2VjYzM2MGY1Yzk4MjE1MmZiOWQyZjY1
10
- NmY4Y2FjY2U4YjczNjliZjFmYzYwYmMzYTNlNTA2MjU4NmVlODFmMzg1NGY0
11
- OWNkN2Y4MGU2ZTM4YjM2YjY0MTZkNjcwMjk0NmQ1YzBkMmNjYjE=
12
- data.tar.gz: !binary |-
13
- Mzg4NGQwNmY2NjdjN2MxZWIzYWE4MWNiOTlkMTc5NGM5NTRiMzRmY2MwNDVh
14
- ODI2YTVhZmUwMzE1OTAwNzA1OWZjMjc3ZGNhZmI1Y2ZmNmU4NmYzNGQ3ZTc1
15
- OGUyZDE0NjZkZTVjNmQxZTJhOWUyNjdhOWIwMjJiMTg2YzA2NWI=
6
+ metadata.gz: 6b376b14646463802f4aee7080663eefdb3d7b1a43088df7126f57c84ecfb0a8730470efe4b57758f2b2079d6bd52b19f2a80293b211095318425d5892194897
7
+ data.tar.gz: fa104b99749d08767074e96f5fea1e97590ee841673660c39d03bb3d2cd50a3d2db2de4d1d195d5503d1bfadec7cbe84e046714f0ab98d0d509bc4c4797949e2
data/.travis.yml CHANGED
@@ -9,9 +9,24 @@ rvm:
9
9
  - 1.9.3
10
10
  - 2.0.0
11
11
  - 2.1.0
12
+ - 2.2.2
12
13
  - jruby-19mode
13
14
  - rbx-2
14
15
  gemfile:
15
16
  - gemfiles/rails_3_2.gemfile
16
17
  - gemfiles/rails_4_1.gemfile
17
18
  - gemfiles/rails_4_2.gemfile
19
+ - gemfiles/rails_5_0.gemfile
20
+ matrix:
21
+ exclude:
22
+ - rvm: 1.9.3
23
+ gemfile: gemfiles/rails_5_0.gemfile
24
+ - rvm: 2.0.0
25
+ gemfile: gemfiles/rails_5_0.gemfile
26
+ - rvm: 2.1.0
27
+ gemfile: gemfiles/rails_5_0.gemfile
28
+ - rvm: jruby-19mode
29
+ gemfile: gemfiles/rails_5_0.gemfile
30
+ - rvm: rbx-2
31
+ gemfile: gemfiles/rails_5_0.gemfile
32
+
data/Appraisals CHANGED
@@ -1,11 +1,21 @@
1
1
  appraise "rails-3-2" do
2
- gem "activerecord", "~> 3.2.21"
2
+ gem "activerecord", "~> 3.2.22.2"
3
+ group :test do
4
+ gem "after_commit_exception_notification"
5
+ end
3
6
  end
4
7
 
5
8
  appraise "rails-4-1" do
6
- gem "activerecord", "~> 4.1.10"
9
+ gem "activerecord", "~> 4.1.16"
10
+ group :test do
11
+ gem "after_commit_exception_notification"
12
+ end
7
13
  end
8
14
 
9
15
  appraise "rails-4-2" do
10
- gem "activerecord", "~> 4.2.1"
16
+ gem "activerecord", "~> 4.2.7"
17
+ end
18
+
19
+ appraise "rails-5-0" do
20
+ gem "activerecord", "~> 5.0.0"
11
21
  end
data/CHANGELOG.md CHANGED
@@ -1,7 +1,82 @@
1
1
  # Change Log
2
2
 
3
+ ## [v0.7.6](https://github.com/swanandp/acts_as_list/tree/v0.7.6) (2016-07-15)
4
+ [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.5...v0.7.6)
5
+
6
+ **Closed issues:**
7
+
8
+ - add\_new\_at nil with scope causes NoMethodError [\#211](https://github.com/swanandp/acts_as_list/issues/211)
9
+
10
+ **Merged pull requests:**
11
+
12
+ - Add class method acts\_as\_list\_top as reader for configured top\_of\_list [\#213](https://github.com/swanandp/acts_as_list/pull/213) ([krzysiek1507](https://github.com/krzysiek1507))
13
+ - Bugfix/add new at nil on scope change [\#212](https://github.com/swanandp/acts_as_list/pull/212) ([greatghoul](https://github.com/greatghoul))
14
+
15
+ ## [v0.7.5](https://github.com/swanandp/acts_as_list/tree/v0.7.5) (2016-06-30)
16
+ [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.4...v0.7.5)
17
+
18
+ **Implemented enhancements:**
19
+
20
+ - Touch when reordering [\#173](https://github.com/swanandp/acts_as_list/pull/173) ([botandrose](https://github.com/botandrose))
21
+
22
+ **Closed issues:**
23
+
24
+ - Exception raised when calling destroy "NameError - instance variable @scope\_changed not defined:" [\#206](https://github.com/swanandp/acts_as_list/issues/206)
25
+ - Undefined instance variable @scope\_changed since 0.7.3 [\#199](https://github.com/swanandp/acts_as_list/issues/199)
26
+ - Reordering large lists is slow [\#198](https://github.com/swanandp/acts_as_list/issues/198)
27
+ - Reparenting child leaves gap in source list in rails 5 [\#194](https://github.com/swanandp/acts_as_list/issues/194)
28
+ - Support rails 5 ? [\#186](https://github.com/swanandp/acts_as_list/issues/186)
29
+ - I get a NoMethodError: undefined method `acts\_as\_list' when trying to include acts\_as\_list [\#176](https://github.com/swanandp/acts_as_list/issues/176)
30
+ - Phenomenon of mysterious value of the position is skipped by one [\#166](https://github.com/swanandp/acts_as_list/issues/166)
31
+ - Model.find being called twice with acts\_as\_list on destroy [\#161](https://github.com/swanandp/acts_as_list/issues/161)
32
+ - `scope\_changed?` problem with acts\_as\_paranoid [\#158](https://github.com/swanandp/acts_as_list/issues/158)
33
+ - Inconsistent behaviour between Symbol and Array scopes [\#155](https://github.com/swanandp/acts_as_list/issues/155)
34
+ - insert\_at doesn't seem to be working in ActiveRecord callback \(Rails 4.2\) [\#150](https://github.com/swanandp/acts_as_list/issues/150)
35
+ - Project Documentation link redirects to expired domain [\#149](https://github.com/swanandp/acts_as_list/issues/149)
36
+ - Problem when updating an position of array of AR objects. [\#137](https://github.com/swanandp/acts_as_list/issues/137)
37
+ - Unexpected behaviour when inserting consecutive items with default positions [\#124](https://github.com/swanandp/acts_as_list/issues/124)
38
+ - self.reload prone to error [\#122](https://github.com/swanandp/acts_as_list/issues/122)
39
+ - Rails 3.0.x in\_list causes the return of default\_scope [\#120](https://github.com/swanandp/acts_as_list/issues/120)
40
+ - Relationships with dependency:destroy cause ActiveRecord::RecordNotFound [\#118](https://github.com/swanandp/acts_as_list/issues/118)
41
+ - Using insert\_at with values with type String [\#117](https://github.com/swanandp/acts_as_list/issues/117)
42
+ - Batch setting of position [\#112](https://github.com/swanandp/acts_as_list/issues/112)
43
+ - position: 0 now makes model pushed to top? [\#110](https://github.com/swanandp/acts_as_list/issues/110)
44
+ - Create element in default position [\#103](https://github.com/swanandp/acts_as_list/issues/103)
45
+ - Enhancement: Expose scope object [\#97](https://github.com/swanandp/acts_as_list/issues/97)
46
+ - Shuffle list [\#96](https://github.com/swanandp/acts_as_list/issues/96)
47
+ - Creating an item with a nil scope should not add it to the list [\#92](https://github.com/swanandp/acts_as_list/issues/92)
48
+ - Performance Improvements [\#88](https://github.com/swanandp/acts_as_list/issues/88)
49
+ - has\_many :through or has\_many\_and\_belongs\_to\_many support [\#86](https://github.com/swanandp/acts_as_list/issues/86)
50
+ - ActiveRecord dependency causes rake assets:compile to fail without access to a database [\#84](https://github.com/swanandp/acts_as_list/issues/84)
51
+ - move\_higher/move\_lower vs move\_to\_top/move\_to\_bottom act differently when item is already at top or bottom [\#77](https://github.com/swanandp/acts_as_list/issues/77)
52
+ - Limiting the list size [\#61](https://github.com/swanandp/acts_as_list/issues/61)
53
+ - Adding multiple creates strange ordering [\#55](https://github.com/swanandp/acts_as_list/issues/55)
54
+ - Feature: sort [\#26](https://github.com/swanandp/acts_as_list/issues/26)
55
+
56
+ **Merged pull requests:**
57
+
58
+ - Removed duplicated assignment [\#207](https://github.com/swanandp/acts_as_list/pull/207) ([shunwen](https://github.com/shunwen))
59
+ - Quote all identifiers [\#205](https://github.com/swanandp/acts_as_list/pull/205) ([fabn](https://github.com/fabn))
60
+ - Start testing Rails 5 [\#203](https://github.com/swanandp/acts_as_list/pull/203) ([brendon](https://github.com/brendon))
61
+ - Lock! the record before destroying [\#201](https://github.com/swanandp/acts_as_list/pull/201) ([brendon](https://github.com/brendon))
62
+ - Fix ambiguous column error when joining some relations [\#180](https://github.com/swanandp/acts_as_list/pull/180) ([natw](https://github.com/natw))
63
+
64
+ ## [v0.7.4](https://github.com/swanandp/acts_as_list/tree/v0.7.4) (2016-04-15)
65
+ [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.3...v0.7.4)
66
+
67
+ **Closed issues:**
68
+
69
+ - Releasing a new gem version [\#196](https://github.com/swanandp/acts_as_list/issues/196)
70
+
71
+ **Merged pull requests:**
72
+
73
+ - Fix scope changed [\#200](https://github.com/swanandp/acts_as_list/pull/200) ([brendon](https://github.com/brendon))
74
+
3
75
  ## [v0.7.3](https://github.com/swanandp/acts_as_list/tree/v0.7.3) (2016-04-14)
4
- [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.7.1...v0.7.3)
76
+ [Full Changelog](https://github.com/swanandp/acts_as_list/compare/v0.7.2...v0.7.3)
77
+
78
+ ## [v0.7.2](https://github.com/swanandp/acts_as_list/tree/v0.7.2) (2016-04-01)
79
+ [Full Changelog](https://github.com/swanandp/acts_as_list/compare/0.7.2...v0.7.2)
5
80
 
6
81
  **Closed issues:**
7
82
 
@@ -238,4 +313,4 @@
238
313
 
239
314
 
240
315
 
241
- \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
316
+ \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
data/Gemfile CHANGED
@@ -9,12 +9,12 @@ platforms :rbx do
9
9
  gem "rubysl-test-unit"
10
10
  end
11
11
 
12
- # Specify your gem"s dependencies in acts_as_list-rails3.gemspec
12
+ gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21, :jruby]
13
+
13
14
  gemspec
14
15
 
15
16
  gem "rake"
16
17
  gem "appraisal"
17
- # Used to automatically generate changelog file
18
18
  gem "github_changelog_generator", "1.9.0"
19
19
 
20
20
  group :test do
data/README.md CHANGED
@@ -95,6 +95,8 @@ default: '1'. Use this option to define the top of the list. Use 0 to make the c
95
95
  default: ':bottom'. Use this option to specify whether objects get added to the :top or :bottom of the list. `nil` will result in new items not being added to the list on create, i.e, position will be kept nil after create.
96
96
 
97
97
  ## Versions
98
+ As of version `0.7.5` Rails 5 is supported.
99
+
98
100
  All versions `0.1.5` onwards require Rails 3.0.x and higher.
99
101
 
100
102
  ## Build Status
@@ -106,7 +108,6 @@ All versions `0.1.5` onwards require Rails 3.0.x and higher.
106
108
  ## Roadmap
107
109
 
108
110
  1. Sort based feature
109
- 2. Rails 4 compatibility and bye bye Rails 2! Older versions would of course continue to work with Rails 2, but there won't be any support on those.
110
111
 
111
112
  ## Contributing to `acts_as_list`
112
113
 
@@ -4,10 +4,11 @@ source "http://rubygems.org"
4
4
 
5
5
  gem "sqlite3", :platforms => [:ruby]
6
6
  gem "activerecord-jdbcsqlite3-adapter", :platforms => [:jruby]
7
+ gem "rack", "~> 1", :platforms => [:ruby_19, :ruby_20, :ruby_21, :jruby]
7
8
  gem "rake"
8
9
  gem "appraisal"
9
10
  gem "github_changelog_generator", "1.9.0"
10
- gem "activerecord", "~> 3.2.21"
11
+ gem "activerecord", "~> 3.2.22.2"
11
12
 
12
13
  group :test do
13
14
  gem "minitest", "~> 5.0"
@@ -4,10 +4,11 @@ source "http://rubygems.org"
4
4
 
5
5
  gem "sqlite3", :platforms => [:ruby]
6
6
  gem "activerecord-jdbcsqlite3-adapter", :platforms => [:jruby]
7
+ gem "rack", "~> 1", :platforms => [:ruby_19, :ruby_20, :ruby_21, :jruby]
7
8
  gem "rake"
8
9
  gem "appraisal"
9
10
  gem "github_changelog_generator", "1.9.0"
10
- gem "activerecord", "~> 4.1.10"
11
+ gem "activerecord", "~> 4.1.16"
11
12
 
12
13
  group :test do
13
14
  gem "minitest", "~> 5.0"
@@ -4,10 +4,11 @@ source "http://rubygems.org"
4
4
 
5
5
  gem "sqlite3", :platforms => [:ruby]
6
6
  gem "activerecord-jdbcsqlite3-adapter", :platforms => [:jruby]
7
+ gem "rack", "~> 1", :platforms => [:ruby_19, :ruby_20, :ruby_21, :jruby]
7
8
  gem "rake"
8
9
  gem "appraisal"
9
10
  gem "github_changelog_generator", "1.9.0"
10
- gem "activerecord", "~> 4.2.1"
11
+ gem "activerecord", "~> 4.2.7"
11
12
 
12
13
  group :test do
13
14
  gem "minitest", "~> 5.0"
@@ -0,0 +1,24 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "sqlite3", :platforms => [:ruby]
6
+ gem "activerecord-jdbcsqlite3-adapter", :platforms => [:jruby]
7
+ gem "rack", "~> 1", :platforms => [:ruby_19, :ruby_20, :ruby_21, :jruby]
8
+ gem "rake"
9
+ gem "appraisal"
10
+ gem "github_changelog_generator", "1.9.0"
11
+ gem "activerecord", "~> 5.0.0"
12
+
13
+ group :test do
14
+ gem "minitest", "~> 5.0"
15
+ gem "test_after_commit", "~> 0.4.2"
16
+ end
17
+
18
+ platforms :rbx do
19
+ gem "rubysl", "~> 2.0"
20
+ gem "rubinius-developer_tools"
21
+ gem "rubysl-test-unit"
22
+ end
23
+
24
+ gemspec :path => "../"
@@ -74,8 +74,13 @@ module ActiveRecord
74
74
  )
75
75
  end
76
76
 
77
+ quoted_position_column = connection.quote_column_name(configuration[:column])
78
+ quoted_position_column_with_table_name = "#{quoted_table_name}.#{quoted_position_column}"
79
+
77
80
  class_eval <<-EOV, __FILE__, __LINE__ + 1
78
- include ::ActiveRecord::Acts::List::InstanceMethods
81
+ def self.acts_as_list_top
82
+ #{configuration[:top_of_list]}.to_i
83
+ end
79
84
 
80
85
  def acts_as_list_top
81
86
  #{configuration[:top_of_list]}.to_i
@@ -111,23 +116,46 @@ module ActiveRecord
111
116
  attr_accessible :#{configuration[:column]}
112
117
  end
113
118
 
114
- before_validation :check_top_position
115
-
116
- before_destroy :reload_position
117
- after_destroy :decrement_positions_on_lower_items
118
-
119
- before_update :check_scope
120
- after_update :update_positions
119
+ scope :in_list, lambda { where(%q{#{quoted_position_column_with_table_name} IS NOT NULL}) }
121
120
 
122
- after_commit 'remove_instance_variable(:@scope_changed) if defined?(@scope_changed)'
121
+ def self.decrement_all
122
+ update_all_with_touch %q(#{quoted_position_column} = (#{quoted_position_column_with_table_name} - 1))
123
+ end
123
124
 
124
- scope :in_list, lambda { where("#{table_name}.#{configuration[:column]} IS NOT NULL") }
125
+ def self.increment_all
126
+ update_all_with_touch %q(#{quoted_position_column} = (#{quoted_position_column_with_table_name} + 1))
127
+ end
128
+
129
+ def self.update_all_with_touch(updates)
130
+ record = new
131
+ attrs = record.send(:timestamp_attributes_for_update_in_model)
132
+ now = record.send(:current_time_from_proper_timezone)
133
+
134
+ query = attrs.map { |attr| %(\#{connection.quote_column_name(attr)} = :now) }
135
+ query.push updates
136
+ query = query.join(", ")
137
+
138
+ update_all([query, now: now])
139
+ end
125
140
  EOV
126
141
 
142
+ attr_reader :position_changed
143
+
144
+ before_validation :check_top_position
145
+
146
+ before_destroy :lock!
147
+ after_destroy :decrement_positions_on_lower_items
148
+
149
+ before_update :check_scope
150
+ after_update :update_positions
151
+
152
+ after_commit :clear_scope_changed
153
+
127
154
  if configuration[:add_new_at].present?
128
- self.send :before_create, :"add_to_list_#{configuration[:add_new_at]}"
155
+ before_create "add_to_list_#{configuration[:add_new_at]}".to_sym
129
156
  end
130
157
 
158
+ include ::ActiveRecord::Acts::List::InstanceMethods
131
159
  end
132
160
  end
133
161
 
@@ -232,10 +260,10 @@ module ActiveRecord
232
260
  limit ||= acts_as_list_list.count
233
261
  position_value = send(position_column)
234
262
  acts_as_list_list.
235
- where("#{position_column} < ?", position_value).
236
- where("#{position_column} >= ?", position_value - limit).
263
+ where("#{quoted_position_column_with_table_name} < ?", position_value).
264
+ where("#{quoted_position_column_with_table_name} >= ?", position_value - limit).
237
265
  limit(limit).
238
- order("#{acts_as_list_class.table_name}.#{position_column} ASC")
266
+ order("#{quoted_position_column_with_table_name} ASC")
239
267
  end
240
268
 
241
269
  # Return the next lower item in the list.
@@ -250,10 +278,10 @@ module ActiveRecord
250
278
  limit ||= acts_as_list_list.count
251
279
  position_value = send(position_column)
252
280
  acts_as_list_list.
253
- where("#{position_column} > ?", position_value).
254
- where("#{position_column} <= ?", position_value + limit).
281
+ where("#{quoted_position_column_with_table_name} > ?", position_value).
282
+ where("#{quoted_position_column_with_table_name} <= ?", position_value + limit).
255
283
  limit(limit).
256
- order("#{acts_as_list_class.table_name}.#{position_column} ASC")
284
+ order("#{quoted_position_column_with_table_name} ASC")
257
285
  end
258
286
 
259
287
  # Test if this record is in a list
@@ -298,7 +326,7 @@ module ActiveRecord
298
326
  # A poorly named method. It will insert the item at the desired position if the position
299
327
  # has been set manually using position=, not necessarily the bottom of the list
300
328
  def add_to_list_bottom
301
- if not_in_list? || internal_scope_changed? && !@position_changed || default_position?
329
+ if not_in_list? || internal_scope_changed? && !position_changed || default_position?
302
330
  self[position_column] = bottom_position_in_list.to_i + 1
303
331
  else
304
332
  increment_positions_on_lower_items(self[position_column], id)
@@ -323,12 +351,11 @@ module ActiveRecord
323
351
 
324
352
  # Returns the bottom item
325
353
  def bottom_item(except = nil)
326
- conditions = scope_condition
327
- conditions = except ? "#{self.class.primary_key} != #{self.class.connection.quote(except.id)}" : {}
354
+ conditions = except ? "#{quoted_table_name}.#{self.class.primary_key} != #{self.class.connection.quote(except.id)}" : {}
328
355
  acts_as_list_list.in_list.where(
329
356
  conditions
330
357
  ).order(
331
- "#{acts_as_list_class.table_name}.#{position_column} DESC"
358
+ "#{quoted_position_column_with_table_name} DESC"
332
359
  ).first
333
360
  end
334
361
 
@@ -344,56 +371,38 @@ module ActiveRecord
344
371
 
345
372
  # This has the effect of moving all the higher items up one.
346
373
  def decrement_positions_on_higher_items(position)
347
- acts_as_list_list.where(
348
- "#{position_column} <= #{position}"
349
- ).update_all(
350
- "#{position_column} = (#{position_column} - 1)"
351
- )
374
+ acts_as_list_list.where("#{quoted_position_column_with_table_name} <= ?", position).decrement_all
352
375
  end
353
376
 
354
377
  # This has the effect of moving all the lower items up one.
355
378
  def decrement_positions_on_lower_items(position=nil)
356
379
  return unless in_list?
357
380
  position ||= send(position_column).to_i
358
- acts_as_list_list.where(
359
- "#{position_column} > #{position}"
360
- ).update_all(
361
- "#{position_column} = (#{position_column} - 1)"
362
- )
381
+ acts_as_list_list.where("#{quoted_position_column_with_table_name} > ?", position).decrement_all
363
382
  end
364
383
 
365
384
  # This has the effect of moving all the higher items down one.
366
385
  def increment_positions_on_higher_items
367
386
  return unless in_list?
368
- acts_as_list_list.where(
369
- "#{position_column} < #{send(position_column).to_i}"
370
- ).update_all(
371
- "#{position_column} = (#{position_column} + 1)"
372
- )
387
+ acts_as_list_list.where("#{quoted_position_column_with_table_name} < #{send(position_column).to_i}").increment_all
373
388
  end
374
389
 
375
390
  # This has the effect of moving all the lower items down one.
376
391
  def increment_positions_on_lower_items(position, avoid_id = nil)
377
- avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
392
+ avoid_id_condition = avoid_id ? " AND #{quoted_table_name}.#{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
378
393
 
379
- acts_as_list_list.where(
380
- "#{position_column} >= #{position}#{avoid_id_condition}"
381
- ).update_all(
382
- "#{position_column} = (#{position_column} + 1)"
383
- )
394
+ acts_as_list_list.where("#{quoted_position_column_with_table_name} >= #{position}#{avoid_id_condition}").increment_all
384
395
  end
385
396
 
386
397
  # Increments position (<tt>position_column</tt>) of all items in the list.
387
398
  def increment_positions_on_all_items
388
- acts_as_list_list.update_all(
389
- "#{position_column} = (#{position_column} + 1)"
390
- )
399
+ acts_as_list_list.increment_all
391
400
  end
392
401
 
393
402
  # Reorders intermediate items to support moving an item from old_position to new_position.
394
403
  def shuffle_positions_on_intermediate_items(old_position, new_position, avoid_id = nil)
395
404
  return if old_position == new_position
396
- avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
405
+ avoid_id_condition = avoid_id ? " AND #{quoted_table_name}.#{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
397
406
 
398
407
  if old_position < new_position
399
408
  # Decrement position of intermediate items
@@ -401,37 +410,35 @@ module ActiveRecord
401
410
  # e.g., if moving an item from 2 to 5,
402
411
  # move [3, 4, 5] to [2, 3, 4]
403
412
  acts_as_list_list.where(
404
- "#{position_column} > #{old_position}"
413
+ "#{quoted_position_column_with_table_name} > ?", old_position
405
414
  ).where(
406
- "#{position_column} <= #{new_position}#{avoid_id_condition}"
407
- ).update_all(
408
- "#{position_column} = (#{position_column} - 1)"
409
- )
415
+ "#{quoted_position_column_with_table_name} <= #{new_position}#{avoid_id_condition}"
416
+ ).decrement_all
410
417
  else
411
418
  # Increment position of intermediate items
412
419
  #
413
420
  # e.g., if moving an item from 5 to 2,
414
421
  # move [2, 3, 4] to [3, 4, 5]
415
422
  acts_as_list_list.where(
416
- "#{position_column} >= #{new_position}"
423
+ "#{quoted_position_column_with_table_name} >= ?", new_position
417
424
  ).where(
418
- "#{position_column} < #{old_position}#{avoid_id_condition}"
419
- ).update_all(
420
- "#{position_column} = (#{position_column} + 1)"
421
- )
425
+ "#{quoted_position_column_with_table_name} < #{old_position}#{avoid_id_condition}"
426
+ ).increment_all
422
427
  end
423
428
  end
424
429
 
425
430
  def insert_at_position(position)
426
431
  return set_list_position(position) if new_record?
427
- if in_list?
428
- old_position = send(position_column).to_i
429
- return if position == old_position
430
- shuffle_positions_on_intermediate_items(old_position, position)
431
- else
432
- increment_positions_on_lower_items(position)
432
+ with_lock do
433
+ if in_list?
434
+ old_position = send(position_column).to_i
435
+ return if position == old_position
436
+ shuffle_positions_on_intermediate_items(old_position, position)
437
+ else
438
+ increment_positions_on_lower_items(position)
439
+ end
440
+ set_list_position(position)
433
441
  end
434
- set_list_position(position)
435
442
  end
436
443
 
437
444
  # used by insert_at_position instead of remove_from_list, as postgresql raises error if position_column has non-null constraint
@@ -448,37 +455,31 @@ module ActiveRecord
448
455
  new_position = send(position_column).to_i
449
456
 
450
457
  return unless acts_as_list_list.where(
451
- "#{position_column} = #{new_position}"
458
+ "#{quoted_position_column_with_table_name} = #{new_position}"
452
459
  ).count > 1
453
460
  shuffle_positions_on_intermediate_items old_position, new_position, id
454
461
  end
455
462
 
456
463
  def internal_scope_changed?
457
464
  return @scope_changed if defined?(@scope_changed)
458
-
465
+
459
466
  @scope_changed = scope_changed?
460
467
  end
461
468
 
462
- # Temporarily swap changes attributes with current attributes
463
- def swap_changed_attributes
464
- @changed_attributes.each do |k, _|
465
- if self.class.column_names.include? k
466
- @changed_attributes[k], self[k] = self[k], @changed_attributes[k]
467
- end
468
- end
469
+ def clear_scope_changed
470
+ remove_instance_variable(:@scope_changed) if defined?(@scope_changed)
469
471
  end
470
472
 
471
473
  def check_scope
472
474
  if internal_scope_changed?
473
- swap_changed_attributes
475
+ cached_changes = changes
476
+
477
+ cached_changes.each { |attribute, values| self[attribute] = values[0] }
474
478
  send('decrement_positions_on_lower_items') if lower_item
475
- swap_changed_attributes
476
- send("add_to_list_#{add_new_at}")
477
- end
478
- end
479
+ cached_changes.each { |attribute, values| self[attribute] = values[1] }
479
480
 
480
- def reload_position
481
- self.reload
481
+ send("add_to_list_#{add_new_at}") if add_new_at.present?
482
+ end
482
483
  end
483
484
 
484
485
  # This check is skipped if the position is currently the default position from the table
@@ -488,6 +489,20 @@ module ActiveRecord
488
489
  self[position_column] = acts_as_list_top
489
490
  end
490
491
  end
492
+
493
+ # When using raw column name it must be quoted otherwise it can raise syntax errors with SQL keywords (e.g. order)
494
+ def quoted_position_column
495
+ @_quoted_position_column ||= self.class.connection.quote_column_name(position_column)
496
+ end
497
+
498
+ # Used in order clauses
499
+ def quoted_table_name
500
+ @_quoted_table_name ||= acts_as_list_class.quoted_table_name
501
+ end
502
+
503
+ def quoted_position_column_with_table_name
504
+ @_quoted_position_column_with_table_name ||= "#{quoted_table_name}.#{quoted_position_column}"
505
+ end
491
506
  end
492
507
  end
493
508
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Acts
3
3
  module List
4
- VERSION = '0.7.4'
4
+ VERSION = '0.7.7'
5
5
  end
6
6
  end
7
7
  end
data/test/helper.rb CHANGED
@@ -12,9 +12,9 @@ require "minitest/autorun"
12
12
  require "#{File.dirname(__FILE__)}/../init"
13
13
 
14
14
  if defined?(ActiveRecord::VERSION) &&
15
- ActiveRecord::VERSION::MAJOR > 4 ||
16
- (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2)
15
+ ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2
17
16
 
17
+ # Was removed in Rails 5 and is effectively true.
18
18
  ActiveRecord::Base.raise_in_transactional_callbacks = true
19
19
  end
20
20
 
data/test/shared.rb CHANGED
@@ -6,4 +6,5 @@ module Shared
6
6
  autoload :ArrayScopeList, 'shared_array_scope_list'
7
7
  autoload :TopAddition, 'shared_top_addition'
8
8
  autoload :NoAddition, 'shared_no_addition'
9
+ autoload :Quoting, 'shared_quoting'
9
10
  end
data/test/shared_list.rb CHANGED
@@ -104,6 +104,12 @@ module Shared
104
104
 
105
105
  new4.reload
106
106
  assert_equal 5, new4.pos
107
+
108
+ last1 = ListMixin.order('pos').last
109
+ last2 = ListMixin.order('pos').last
110
+ last1.insert_at(1)
111
+ last2.insert_at(1)
112
+ assert_equal [1, 2, 3, 4, 5], ListMixin.where(parent_id: 20).order('pos').map(&:pos)
107
113
  end
108
114
 
109
115
  def test_delete_middle
@@ -142,7 +148,7 @@ module Shared
142
148
 
143
149
  def test_update_position_when_scope_changes
144
150
  assert_equal [1, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
145
- parent = ListMixin.create(parent_id: 6)
151
+ ListMixin.create(parent_id: 6)
146
152
 
147
153
  ListMixin.where(id: 2).first.move_within_scope(6)
148
154
 
@@ -246,5 +252,11 @@ module Shared
246
252
 
247
253
  assert_equal [5, 1, 6, 2, 3, 4], ListMixin.where(parent_id: 5).order('pos').map(&:id)
248
254
  end
255
+
256
+ def test_non_persisted_records_dont_get_lock_called
257
+ new = ListMixin.new(parent_id: 5)
258
+
259
+ new.destroy
260
+ end
249
261
  end
250
262
  end
@@ -21,5 +21,16 @@ module Shared
21
21
  assert !new.in_list?
22
22
  end
23
23
 
24
+ def test_update_scope_does_not_add_to_list
25
+ new = NoAdditionMixin.create
26
+
27
+ new.update_attribute(:parent_id, 20)
28
+ new.reload
29
+ assert !new.in_list?
30
+
31
+ new.update_attribute(:parent_id, 5)
32
+ new.reload
33
+ assert !new.in_list?
34
+ end
24
35
  end
25
36
  end
@@ -0,0 +1,21 @@
1
+ module Shared
2
+ module Quoting
3
+
4
+ def setup
5
+ 3.times { |counter| QuotedList.create! order: counter }
6
+ end
7
+
8
+ def test_create
9
+ assert_equal QuotedList.in_list.size, 3
10
+ end
11
+
12
+ # This test execute raw queries involving table name
13
+ def test_moving
14
+ item = QuotedList.first
15
+ item.higher_items
16
+ item.lower_items
17
+ item.send :bottom_item # Part of private api
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,64 @@
1
+ require 'helper'
2
+
3
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
4
+ ActiveRecord::Schema.verbose = false
5
+
6
+ class Section < ActiveRecord::Base
7
+ has_many :items
8
+ acts_as_list
9
+
10
+ scope :visible, -> { where(visible: true) }
11
+ end
12
+
13
+ class Item < ActiveRecord::Base
14
+ belongs_to :section
15
+ acts_as_list scope: :section
16
+
17
+ scope :visible, -> { where(visible: true).joins(:section).merge(Section.visible) }
18
+ end
19
+
20
+ class JoinedTestCase < Minitest::Test
21
+ def setup
22
+ ActiveRecord::Base.connection.create_table :sections do |t|
23
+ t.column :position, :integer
24
+ t.column :visible, :boolean, default: true
25
+ end
26
+
27
+ ActiveRecord::Base.connection.create_table :items do |t|
28
+ t.column :position, :integer
29
+ t.column :section_id, :integer
30
+ t.column :visible, :boolean, default: true
31
+ end
32
+
33
+ ActiveRecord::Base.connection.schema_cache.clear!
34
+ [Section, Item].each(&:reset_column_information)
35
+ super
36
+ end
37
+
38
+ def teardown
39
+ ActiveRecord::Base.connection.tables.each do |table|
40
+ ActiveRecord::Base.connection.drop_table(table)
41
+ end
42
+ super
43
+ end
44
+ end
45
+
46
+ # joining the relation returned by `#higher_items` or `#lower_items` to another table
47
+ # previously could result in ambiguous column names in the query
48
+ class TestHigherLowerItems < JoinedTestCase
49
+ def test_higher_items
50
+ section = Section.create
51
+ item1 = Item.create section: section
52
+ item2 = Item.create section: section
53
+ item3 = Item.create section: section
54
+ assert_equal item3.higher_items.visible, [item1, item2]
55
+ end
56
+
57
+ def test_lower_items
58
+ section = Section.create
59
+ item1 = Item.create section: section
60
+ item2 = Item.create section: section
61
+ item3 = Item.create section: section
62
+ assert_equal item1.lower_items.visible, [item2, item3]
63
+ end
64
+ end
data/test/test_list.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # NOTE: following now done in helper.rb (better Readability)
2
2
  require 'helper'
3
3
 
4
- ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
4
+ ActiveRecord::Base.establish_connection(
5
+ adapter: "sqlite3",
6
+ database: 'file:memdb1?mode=memory&cache=shared'
7
+ )
5
8
  ActiveRecord::Schema.verbose = false
6
9
 
7
10
  def setup_db(position_options = {})
@@ -16,9 +19,14 @@ def setup_db(position_options = {})
16
19
  t.column :state, :integer
17
20
  end
18
21
 
22
+ # This table is used to test table names and column names quoting
23
+ ActiveRecord::Base.connection.create_table 'table-name' do |t|
24
+ t.column :order, :integer
25
+ end
26
+
19
27
  mixins = [ Mixin, ListMixin, ListMixinSub1, ListMixinSub2, ListWithStringScopeMixin,
20
28
  ArrayScopeListMixin, ZeroBasedMixin, DefaultScopedMixin,
21
- DefaultScopedWhereMixin, TopAdditionMixin, NoAdditionMixin ]
29
+ DefaultScopedWhereMixin, TopAdditionMixin, NoAdditionMixin, QuotedList ]
22
30
 
23
31
  mixins << EnumArrayScopeListMixin if rails_4
24
32
 
@@ -42,7 +50,13 @@ def rails_4
42
50
  end
43
51
 
44
52
  def teardown_db
45
- ActiveRecord::Base.connection.tables.each do |table|
53
+ if ActiveRecord::VERSION::MAJOR >= 5
54
+ tables = ActiveRecord::Base.connection.data_sources
55
+ else
56
+ tables = ActiveRecord::Base.connection.tables
57
+ end
58
+
59
+ tables.each do |table|
46
60
  ActiveRecord::Base.connection.drop_table(table)
47
61
  end
48
62
  end
@@ -149,6 +163,18 @@ end
149
163
  class TheBaseSubclass < TheBaseClass
150
164
  end
151
165
 
166
+ class DBConfigTest < Minitest::Test
167
+ def test_db_config
168
+ # make sure sqlite3 accepts multi threaded access
169
+ assert_equal "file:memdb1?mode=memory&cache=shared", ActiveRecord::Base.connection.pool.spec.config[:database]
170
+ end
171
+ end
172
+
173
+ class QuotedList < ActiveRecord::Base
174
+ self.table_name = 'table-name'
175
+ acts_as_list column: :order
176
+ end
177
+
152
178
  class ActsAsListTestCase < Minitest::Test
153
179
  # No default test required as this class is abstract.
154
180
  # Need for test/unit.
@@ -184,6 +210,37 @@ class ListTest < ActsAsListTestCase
184
210
  setup_db
185
211
  super
186
212
  end
213
+
214
+ def test_insert_race_condition
215
+ # the bigger n is the more likely we will have a race condition
216
+ n = 1000
217
+ (1..n).each do |counter|
218
+ node = ListMixin.new parent_id: 1
219
+ node.pos = counter
220
+ node.save!
221
+ end
222
+
223
+ wait_for_it = true
224
+ threads = []
225
+ 4.times do |i|
226
+ threads << Thread.new do
227
+ true while wait_for_it
228
+ ActiveRecord::Base.connection_pool.with_connection do |c|
229
+ n.times do
230
+ begin
231
+ ListMixin.where(parent_id: 1).order(:pos).last.insert_at(1)
232
+ rescue Exception
233
+ # ignore SQLite3::SQLException due to table locking
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+ wait_for_it = false
240
+ threads.each(&:join)
241
+
242
+ assert_equal (1..n).to_a, ListMixin.where(parent_id: 1).order('pos').map(&:pos)
243
+ end
187
244
  end
188
245
 
189
246
  class ListWithCallbackTest < ActsAsListTestCase
@@ -243,6 +300,15 @@ class ArrayScopeListTestWithDefault < ActsAsListTestCase
243
300
  end
244
301
  end
245
302
 
303
+ class QuotingTestList < ActsAsListTestCase
304
+ include Shared::Quoting
305
+
306
+ def setup
307
+ setup_db_with_default
308
+ super
309
+ end
310
+ end
311
+
246
312
  class DefaultScopedTest < ActsAsListTestCase
247
313
  def setup
248
314
  setup_db
@@ -603,3 +669,82 @@ class MultipleListsArrayScopeTest < ActsAsListTestCase
603
669
  assert_equal [1], ArrayScopeListMixin.where(:parent_id => 4, :parent_type => 'anything').order(:pos).map(&:pos)
604
670
  end
605
671
  end
672
+
673
+ class TouchTest < ActsAsListTestCase
674
+ def setup
675
+ setup_db
676
+ 4.times { ListMixin.create! updated_at: yesterday }
677
+ end
678
+
679
+ def now
680
+ Time.now.utc
681
+ end
682
+
683
+ def yesterday
684
+ 1.day.ago
685
+ end
686
+
687
+ def updated_ats
688
+ ListMixin.pluck(:updated_at)
689
+ end
690
+
691
+ def test_moving_item_lower_touches_self_and_lower_item
692
+ ListMixin.first.move_lower
693
+ updated_ats[0..1].each do |updated_at|
694
+ assert_in_delta updated_at, now, 1.second
695
+ end
696
+ updated_ats[2..3].each do |updated_at|
697
+ assert_in_delta updated_at, yesterday, 1.second
698
+ end
699
+ end
700
+
701
+ def test_moving_item_higher_touches_self_and_higher_item
702
+ ListMixin.all.second.move_higher
703
+ updated_ats[0..1].each do |updated_at|
704
+ assert_in_delta updated_at, now, 1.second
705
+ end
706
+ updated_ats[2..3].each do |updated_at|
707
+ assert_in_delta updated_at, yesterday, 1.second
708
+ end
709
+ end
710
+
711
+ def test_moving_item_to_bottom_touches_all_other_items
712
+ ListMixin.first.move_to_bottom
713
+ updated_ats.each do |updated_at|
714
+ assert_in_delta updated_at, now, 1.second
715
+ end
716
+ end
717
+
718
+ def test_moving_item_to_top_touches_all_other_items
719
+ ListMixin.last.move_to_top
720
+ updated_ats.each do |updated_at|
721
+ assert_in_delta updated_at, now, 1.second
722
+ end
723
+ end
724
+
725
+ def test_removing_item_touches_all_lower_items
726
+ ListMixin.all.third.remove_from_list
727
+ updated_ats[0..1].each do |updated_at|
728
+ assert_in_delta updated_at, yesterday, 1.second
729
+ end
730
+ updated_ats[2..2].each do |updated_at|
731
+ assert_in_delta updated_at, now, 1.second
732
+ end
733
+ end
734
+ end
735
+
736
+ class ActsAsListTopTest < ActsAsListTestCase
737
+ def setup
738
+ setup_db
739
+ end
740
+
741
+ def test_acts_as_list_top
742
+ assert_equal 1, TheBaseSubclass.new.acts_as_list_top
743
+ assert_equal 0, ZeroBasedMixin.new.acts_as_list_top
744
+ end
745
+
746
+ def test_class_acts_as_list_top
747
+ assert_equal 1, TheBaseSubclass.acts_as_list_top
748
+ assert_equal 0, ZeroBasedMixin.acts_as_list_top
749
+ end
750
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_list
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -10,34 +10,34 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-04-15 00:00:00.000000000 Z
13
+ date: 2016-08-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - ! '>='
19
+ - - ">="
20
20
  - !ruby/object:Gem::Version
21
21
  version: '3.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - ! '>='
26
+ - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  version: '3.0'
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: bundler
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
- - - ! '>='
33
+ - - ">="
34
34
  - !ruby/object:Gem::Version
35
35
  version: 1.0.0
36
36
  type: :development
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
- - - ! '>='
40
+ - - ">="
41
41
  - !ruby/object:Gem::Version
42
42
  version: 1.0.0
43
43
  description: This "acts_as" extension provides the capabilities for sorting and reordering
@@ -49,9 +49,9 @@ executables: []
49
49
  extensions: []
50
50
  extra_rdoc_files: []
51
51
  files:
52
- - .gemtest
53
- - .gitignore
54
- - .travis.yml
52
+ - ".gemtest"
53
+ - ".gitignore"
54
+ - ".travis.yml"
55
55
  - Appraisals
56
56
  - CHANGELOG.md
57
57
  - Gemfile
@@ -62,6 +62,7 @@ files:
62
62
  - gemfiles/rails_3_2.gemfile
63
63
  - gemfiles/rails_4_1.gemfile
64
64
  - gemfiles/rails_4_2.gemfile
65
+ - gemfiles/rails_5_0.gemfile
65
66
  - init.rb
66
67
  - lib/acts_as_list.rb
67
68
  - lib/acts_as_list/active_record/acts/list.rb
@@ -72,8 +73,10 @@ files:
72
73
  - test/shared_list.rb
73
74
  - test/shared_list_sub.rb
74
75
  - test/shared_no_addition.rb
76
+ - test/shared_quoting.rb
75
77
  - test/shared_top_addition.rb
76
78
  - test/shared_zero_based.rb
79
+ - test/test_joined_list.rb
77
80
  - test/test_list.rb
78
81
  homepage: http://github.com/swanandp/acts_as_list
79
82
  licenses:
@@ -85,12 +88,12 @@ require_paths:
85
88
  - lib
86
89
  required_ruby_version: !ruby/object:Gem::Requirement
87
90
  requirements:
88
- - - ! '>='
91
+ - - ">="
89
92
  - !ruby/object:Gem::Version
90
93
  version: 1.9.2
91
94
  required_rubygems_version: !ruby/object:Gem::Requirement
92
95
  requirements:
93
- - - ! '>='
96
+ - - ">="
94
97
  - !ruby/object:Gem::Version
95
98
  version: '0'
96
99
  requirements: []
@@ -107,6 +110,8 @@ test_files:
107
110
  - test/shared_list.rb
108
111
  - test/shared_list_sub.rb
109
112
  - test/shared_no_addition.rb
113
+ - test/shared_quoting.rb
110
114
  - test/shared_top_addition.rb
111
115
  - test/shared_zero_based.rb
116
+ - test/test_joined_list.rb
112
117
  - test/test_list.rb