acts_as_list 0.7.4 → 0.7.7

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