acts_as_list 0.9.19 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 01e6f5e3817634aa371273af2786776b0e0dd9e5
4
- data.tar.gz: c305a4fa8ea72eebbac9aa74a1933d6713741d1a
2
+ SHA256:
3
+ metadata.gz: 3593a0d6b48448637a21329267b94cb13953a87687e544167bc486498fde2bd3
4
+ data.tar.gz: 7934c33d56e6f7ef76fd968fc337fadf534eecb8578bca9b657f8e15ab4bc9ff
5
5
  SHA512:
6
- metadata.gz: 99c8762a0530f4f3e2cce408eb156f9a6eaa85a3b9d9194ad1f0858f4d47095365726a29a188b8b69a9d7a7d70c4bb828db1386c3c46d23134e341c56fac8731
7
- data.tar.gz: 9faea6f72ce9cc2ea854eaa921390d5edc212a1a94018e149fae835fd480dc8175bbcc5a8645fecd29763bea00d7950d06222972b1d9e1bef2087cd540c4e471
6
+ metadata.gz: ffee85cbf68f822e30b5ba670646b28725a6413a1b5ec51107fa902121a105765923dfb592824a7421b04ada6017285cb574daea3370b78b045a90456d0c038c
7
+ data.tar.gz: d6a3ead907c8cdc67d2add03a33c3c3f5a1612965406997869b7ec3ad1be1820fb52033f2ba18e6284f925027e3ef1f29c58cafaecf630986f9d143583299cf5
@@ -1,58 +1,29 @@
1
1
  language: ruby
2
2
  cache: bundler
3
- # Explicit usage of containerized builds, should provide faster feedback
4
- # see https://docs.travis-ci.com/user/workers/container-based-infrastructure/
5
- # and https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments
6
3
  sudo: false
7
- before_install:
8
- - gem install bundler -v 1.16.1
9
4
  before_script:
5
+ - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
6
+ - gem install bundler -v '< 2'
10
7
  - mysql -e 'create database acts_as_list;'
11
8
  - psql -c 'create database acts_as_list;' -U postgres
12
9
  rvm:
13
- - 1.9.3
14
- - 2.0.0
15
- - 2.1.10
16
- - 2.2.6
17
- - 2.3.6
18
- - 2.4.3
19
- - 2.5.0
10
+ - 2.4.7
11
+ - 2.5.6
12
+ - 2.6.4
13
+ services:
14
+ - mysql
15
+ - postgresql
20
16
  env:
21
17
  - DB=sqlite
22
18
  - DB=mysql
23
19
  - DB=postgresql
24
20
  gemfile:
25
- - gemfiles/rails_3_2.gemfile
26
- - gemfiles/rails_4_1.gemfile
27
21
  - gemfiles/rails_4_2.gemfile
28
22
  - gemfiles/rails_5_0.gemfile
29
23
  - gemfiles/rails_5_1.gemfile
30
24
  - gemfiles/rails_5_2.gemfile
25
+ - gemfiles/rails_6_0.gemfile
31
26
  matrix:
32
27
  exclude:
33
- - rvm: 1.9.3
34
- gemfile: gemfiles/rails_5_0.gemfile
35
- - rvm: 1.9.3
36
- gemfile: gemfiles/rails_5_1.gemfile
37
- - rvm: 1.9.3
38
- gemfile: gemfiles/rails_5_2.gemfile
39
- - rvm: 2.0.0
40
- gemfile: gemfiles/rails_5_0.gemfile
41
- - rvm: 2.0.0
42
- gemfile: gemfiles/rails_5_1.gemfile
43
- - rvm: 2.0.0
44
- gemfile: gemfiles/rails_5_2.gemfile
45
- - rvm: 2.1.10
46
- gemfile: gemfiles/rails_5_0.gemfile
47
- - rvm: 2.1.10
48
- gemfile: gemfiles/rails_5_1.gemfile
49
- - rvm: 2.1.10
50
- gemfile: gemfiles/rails_5_2.gemfile
51
- - rvm: 2.4.3
52
- gemfile: gemfiles/rails_3_2.gemfile
53
- - rvm: 2.4.3
54
- gemfile: gemfiles/rails_4_1.gemfile
55
- - rvm: 2.5.0
56
- gemfile: gemfiles/rails_3_2.gemfile
57
- - rvm: 2.5.0
58
- gemfile: gemfiles/rails_4_1.gemfile
28
+ - rvm: 2.4.7
29
+ gemfile: gemfiles/rails_6_0.gemfile
data/Appraisals CHANGED
@@ -1,50 +1,31 @@
1
- appraise "rails-3-2" do
1
+ appraise "rails-4-2" do
2
2
  group :mysql do
3
- gem "mysql2", "~> 0.3.21", platforms: [:ruby]
3
+ gem "mysql2", "~> 0.4.0"
4
4
  end
5
- gem "activerecord", "~> 3.2.22.2"
6
- gem "rake", "~> 12.2.0", platforms: [:ruby_19]
7
- group :test do
8
- gem "after_commit_exception_notification"
5
+ group :postgresql do
6
+ gem "pg", "~> 0.18.4"
9
7
  end
10
- end
11
-
12
- appraise "rails-4-1" do
13
- group :mysql do
14
- gem "mysql2", "~> 0.3.21", platforms: [:ruby]
15
- end
16
- gem "activerecord", "~> 4.1.16"
17
- gem "rake", "~> 12.2.0", platforms: [:ruby_19]
18
8
  group :test do
19
- gem "after_commit_exception_notification"
20
- end
21
- end
22
-
23
- appraise "rails-4-2" do
24
- group :mysql do
25
- gem "mysql2", "~> 0.4.10", platforms: [:ruby]
9
+ gem "test_after_commit", "~> 0.4.2"
26
10
  end
27
- gem "activerecord", "~> 4.2.10"
28
- gem "rake", "~> 12.2.0", platforms: [:ruby_19]
11
+ gem "activerecord", "~> 4.2.0"
29
12
  end
30
13
 
31
14
  appraise "rails-5-0" do
32
- group :mysql do
33
- gem "mysql2", "~> 0.4.10", platforms: [:ruby]
34
- end
35
- gem "activerecord", "~> 5.0.6"
15
+ gem "activerecord", "~> 5.0.0"
36
16
  end
37
17
 
38
18
  appraise "rails-5-1" do
39
- group :mysql do
40
- gem "mysql2", "~> 0.4.10", platforms: [:ruby]
41
- end
42
- gem "activerecord", "~> 5.1.4"
19
+ gem "activerecord", "~> 5.1.0"
43
20
  end
44
21
 
45
22
  appraise "rails-5-2" do
46
- group :mysql do
47
- gem "mysql2", "~> 0.4.10", platforms: [:ruby]
23
+ gem "activerecord", "~> 5.2.0"
24
+ end
25
+
26
+ appraise "rails-6-0" do
27
+ group :sqlite do
28
+ gem "sqlite3", "~> 1.4"
48
29
  end
49
- gem "activerecord", "~> 5.2.1"
30
+ gem "activerecord", "~> 6.0.0"
50
31
  end
@@ -5,6 +5,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
+ ### Removed
9
+ - **BREAKING CHANGE**: Support for Rails 3.2 > 4.1 has been removed. 0.9.19 is the last version that supports
10
+ these Rails versions
11
+
12
+ ### Added
13
+ - Added *Troubleshooting Database Deadlock Errors* section to `README.md`
14
+ - Added support for Rails 6.0 in testing
15
+ - Various README fixes
16
+ - A new method called `current_position` now exists and returns the integer position of the item it's
17
+ called on, or `nil` if the position isn't set.
8
18
 
9
19
  ## [0.9.19] - 2019-03-12
10
20
  ### Added
data/Gemfile CHANGED
@@ -1,7 +1,5 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
4
-
5
3
  gemspec
6
4
 
7
5
  gem "rake"
@@ -13,15 +11,18 @@ end
13
11
 
14
12
  group :test do
15
13
  gem "minitest", "~> 5.0"
16
- gem "test_after_commit", "~> 0.4.2"
17
14
  gem "timecop"
18
15
  gem "mocha"
19
16
  end
20
17
 
21
18
  group :sqlite do
22
- gem "sqlite3", "~> 1.3.13", platforms: [:ruby]
19
+ gem "sqlite3", "~> 1.3.13"
23
20
  end
24
21
 
25
22
  group :postgresql do
26
- gem "pg", "~> 0.18.0", platforms: [:ruby]
23
+ gem "pg", "~> 1.1.4"
24
+ end
25
+
26
+ group :mysql do
27
+ gem "mysql2", "~> 0.5.0"
27
28
  end
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # ActsAsList
1
+ # Acts As List
2
2
 
3
3
  ## Build Status
4
- [![Build Status](https://secure.travis-ci.org/swanandp/acts_as_list.png)](https://secure.travis-ci.org/swanandp/acts_as_list)
4
+ [![Build Status](https://travis-ci.org/brendon/acts_as_list.svg?branch=master)](https://travis-ci.org/brendon/acts_as_list)
5
5
  [![Gem Version](https://badge.fury.io/rb/acts_as_list.svg)](https://badge.fury.io/rb/acts_as_list)
6
6
 
7
7
  ## Description
@@ -142,6 +142,8 @@ default: `position`. Use this option if the column name in your database is diff
142
142
  default: `1`. Use this option to define the top of the list. Use 0 to make the collection act more like an array in its indexing.
143
143
  - `add_new_at`
144
144
  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.
145
+ - `touch_on_update`
146
+ default: `true`. Use `touch_on_update: false` if you don't want to update the timestamps of the associated records.
145
147
 
146
148
  ## Disabling temporarily
147
149
 
@@ -181,6 +183,73 @@ TodoItem.acts_as_list_no_update([TodoAttachment]) do
181
183
  end
182
184
  ```
183
185
 
186
+ ## Troubleshooting Database Deadlock Errors
187
+ When using this gem in an app with a high amount of concurrency, you may see "deadlock" errors raised by your database server.
188
+ It's difficult for the gem to provide a solution that fits every app.
189
+ Here are some steps you can take to mitigate and handle these kinds of errors.
190
+
191
+ ### 1) Use the Most Concise API
192
+ One easy way to reduce deadlocks is to use the most concise gem API available for what you want to accomplish.
193
+ In this specific example, the more concise API for creating a list item at a position results in one transaction instead of two,
194
+ and it issues fewer SQL statements. Issuing fewer statements tends to lead to faster transactions.
195
+ Faster transactions are less likely to deadlock.
196
+
197
+ Example:
198
+ ```ruby
199
+ # Good
200
+ TodoItem.create(todo_list: todo_list, position: 1)
201
+
202
+ # Bad
203
+ item = TodoItem.create(todo_list: todo_list)
204
+ item.insert_at(1)
205
+ ```
206
+
207
+ ### 2) Rescue then Retry
208
+ Deadlocks are always a possibility when updating tables rows concurrently.
209
+ The general advice from MySQL documentation is to catch these errors and simply retry the transaction; it will probably succeed on another attempt. (see [How to Minimize and Handle Deadlocks](https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html))
210
+ Retrying transactions sounds simple, but there are many details that need to be chosen on a per-app basis:
211
+ How many retry attempts should be made?
212
+ Should there be a wait time between attempts?
213
+ What _other_ statements were in the transaction that got rolled back?
214
+
215
+ Here a simple example of rescuing from deadlock and retrying the operation:
216
+ * `ActiveRecord::Deadlocked` is available in Rails >= 5.1.0.
217
+ * If you have Rails < 5.1.0, you will need to rescue `ActiveRecord::StatementInvalid` and check `#cause`.
218
+ ```ruby
219
+ attempts_left = 2
220
+ while attempts_left > 0
221
+ attempts_left -= 1
222
+ begin
223
+ TodoItem.transaction do
224
+ TodoItem.create(todo_list: todo_list, position: 1)
225
+ end
226
+ attempts_left = 0
227
+ rescue ActiveRecord::Deadlocked
228
+ raise unless attempts_left > 0
229
+ end
230
+ end
231
+ ```
232
+
233
+ You can also use the approach suggested in this StackOverflow post:
234
+ https://stackoverflow.com/questions/4027659/activerecord3-deadlock-retry
235
+
236
+ ### 3) Lock Parent Record
237
+ In addition to reacting to deadlocks, it is possible to reduce their frequency with more pessimistic locking.
238
+ This approach uses the parent record as a mutex for the entire list.
239
+ This kind of locking is very effective at reducing the frequency of deadlocks while updating list items.
240
+ However, there are some things to keep in mind:
241
+ * This locking pattern needs to be used around *every* call that modifies the list; even if it does not reorder list items.
242
+ * This locking pattern effectively serializes operations on the list. The throughput of operations on the list will decrease.
243
+ * Locking the parent record may lead to deadlock elsewhere if some other code also locks the parent table.
244
+
245
+ Example:
246
+ ```ruby
247
+ todo_list = TodoList.create(name: "The List")
248
+ todo_list.with_lock do
249
+ item = TodoItem.create(description: "Buy Groceries", todo_list: todo_list, position: 1)
250
+ end
251
+ ```
252
+
184
253
  ## Versions
185
254
  Version `0.9.0` adds `acts_as_list_no_update` (https://github.com/swanandp/acts_as_list/pull/244) and compatibility with not-null and uniqueness constraints on the database (https://github.com/swanandp/acts_as_list/pull/246). These additions shouldn't break compatibility with existing implementations.
186
255
 
@@ -188,8 +257,15 @@ As of version `0.7.5` Rails 5 is supported.
188
257
 
189
258
  All versions `0.1.5` onwards require Rails 3.0.x and higher.
190
259
 
191
- ## Workflow Status
192
- [![WIP Issues](https://badge.waffle.io/swanandp/acts_as_list.png)](http://waffle.io/swanandp/acts_as_list)
260
+ ## A note about data integrity
261
+
262
+ We often hear complaints that `position` values are repeated, incorrect etc. For example, #254. To ensure data integrity, you should rely on your database. There are two things you can do:
263
+
264
+ 1. Use constraints. If you model `Item` that `belongs_to` an `Order`, and it has a `position` column, then add a unique constraint on `items` with `[:order_id, :position_id]`. Think of it as a list invariant. What are the properties of your list that don't change no matter how many items you have in it? One such propery is that each item has a distinct position. Another _could be_ that position is always greater than 0. It is strongly recommended that you rely on your database to enforce these invariants or constraints. Here are the docs for [PostgreSQL](https://www.postgresql.org/docs/9.5/static/ddl-constraints.html) and [MySQL](https://dev.mysql.com/doc/refman/8.0/en/alter-table.html).
265
+ 2. Use mutexes or row level locks. At its heart the duplicate problem is that of handling concurrency. Adding a contention resolution mechanism like locks will solve it to some extent. But it is not a solution or replacement for constraints. Locks are also prone to deadlocks.
266
+
267
+ As a library, `acts_as_list` may not always have all the context needed to apply these tools. They are much better suited at the application level.
268
+
193
269
 
194
270
  ## Roadmap
195
271
 
@@ -7,29 +7,29 @@ Gem::Specification.new do |s|
7
7
  s.name = "acts_as_list"
8
8
  s.version = ActiveRecord::Acts::List::VERSION
9
9
  s.platform = Gem::Platform::RUBY
10
- s.authors = ["David Heinemeier Hansson", "Swanand Pagnis", "Quinn Chaffee"]
11
- s.email = ["swanand.pagnis@gmail.com"]
10
+ s.authors = ["Swanand Pagnis", "Brendon Muir"]
11
+ s.email = %w(swanand.pagnis@gmail.com brendon@spikeatschool.co.nz)
12
12
  s.homepage = "http://github.com/swanandp/acts_as_list"
13
13
  s.summary = "A gem adding sorting, reordering capabilities to an active_record model, allowing it to act as a list"
14
14
  s.description = 'This "acts_as" extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a "position" column defined as an integer on the mapped database table.'
15
15
  s.license = "MIT"
16
16
  s.rubyforge_project = "acts_as_list"
17
- s.required_ruby_version = ">= 1.9.2"
17
+ s.required_ruby_version = ">= 2.4.7"
18
18
 
19
19
  if s.respond_to?(:metadata)
20
- s.metadata['changelog_uri'] = 'https://github.com/swanandp/acts_as_list/blob/master/CHANGELOG.md'
20
+ s.metadata['changelog_uri'] = 'https://github.com/swanandp/acts_as_list/blob/master/CHANGELOG.md'
21
21
  s.metadata['source_code_uri'] = 'https://github.com/swanandp/acts_as_list'
22
22
  s.metadata['bug_tracker_uri'] = 'https://github.com/swanandp/acts_as_list/issues'
23
23
  end
24
24
 
25
25
  # Load Paths...
26
- s.files = `git ls-files`.split("\n")
27
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
- s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
29
- s.require_paths = ["lib"]
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.executables = `git ls-files -- bin/*`.split("\n").map {|f| File.basename(f)}
29
+ s.require_paths = ["lib"]
30
30
 
31
31
 
32
32
  # Dependencies (installed via "bundle install")
33
- s.add_dependency "activerecord", ">= 3.0"
33
+ s.add_dependency "activerecord", ">= 4.2"
34
34
  s.add_development_dependency "bundler", ">= 1.0.0"
35
35
  end
@@ -2,10 +2,9 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
6
- gem "rake", "~> 12.2.0", platforms: [:ruby_19]
5
+ gem "rake"
7
6
  gem "appraisal"
8
- gem "activerecord", "~> 4.2.10"
7
+ gem "activerecord", "~> 4.2.0"
9
8
 
10
9
  group :development do
11
10
  gem "github_changelog_generator", "1.9.0"
@@ -13,21 +12,21 @@ end
13
12
 
14
13
  group :test do
15
14
  gem "minitest", "~> 5.0"
16
- gem "test_after_commit", "~> 0.4.2"
17
15
  gem "timecop"
18
16
  gem "mocha"
17
+ gem "test_after_commit", "~> 0.4.2"
19
18
  end
20
19
 
21
20
  group :sqlite do
22
- gem "sqlite3", "~> 1.3.13", platforms: [:ruby]
21
+ gem "sqlite3", "~> 1.3.13"
23
22
  end
24
23
 
25
24
  group :postgresql do
26
- gem "pg", "~> 0.18.0", platforms: [:ruby]
25
+ gem "pg", "~> 0.18.4"
27
26
  end
28
27
 
29
28
  group :mysql do
30
- gem "mysql2", "~> 0.4.10", platforms: [:ruby]
29
+ gem "mysql2", "~> 0.4.0"
31
30
  end
32
31
 
33
32
  gemspec path: "../"
@@ -2,10 +2,9 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
6
5
  gem "rake"
7
6
  gem "appraisal"
8
- gem "activerecord", "~> 5.0.6"
7
+ gem "activerecord", "~> 5.0.0"
9
8
 
10
9
  group :development do
11
10
  gem "github_changelog_generator", "1.9.0"
@@ -13,21 +12,20 @@ end
13
12
 
14
13
  group :test do
15
14
  gem "minitest", "~> 5.0"
16
- gem "test_after_commit", "~> 0.4.2"
17
15
  gem "timecop"
18
16
  gem "mocha"
19
17
  end
20
18
 
21
19
  group :sqlite do
22
- gem "sqlite3", "~> 1.3.13", platforms: [:ruby]
20
+ gem "sqlite3", "~> 1.3.13"
23
21
  end
24
22
 
25
23
  group :postgresql do
26
- gem "pg", "~> 0.18.0", platforms: [:ruby]
24
+ gem "pg", "~> 1.1.4"
27
25
  end
28
26
 
29
27
  group :mysql do
30
- gem "mysql2", "~> 0.4.10", platforms: [:ruby]
28
+ gem "mysql2", "~> 0.5.0"
31
29
  end
32
30
 
33
31
  gemspec path: "../"
@@ -2,10 +2,9 @@
2
2
 
3
3
  source "http://rubygems.org"
4
4
 
5
- gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
6
5
  gem "rake"
7
6
  gem "appraisal"
8
- gem "activerecord", "~> 5.1.4"
7
+ gem "activerecord", "~> 5.1.0"
9
8
 
10
9
  group :development do
11
10
  gem "github_changelog_generator", "1.9.0"
@@ -13,21 +12,20 @@ end
13
12
 
14
13
  group :test do
15
14
  gem "minitest", "~> 5.0"
16
- gem "test_after_commit", "~> 0.4.2"
17
15
  gem "timecop"
18
16
  gem "mocha"
19
17
  end
20
18
 
21
19
  group :sqlite do
22
- gem "sqlite3", "~> 1.3.13", platforms: [:ruby]
20
+ gem "sqlite3", "~> 1.3.13"
23
21
  end
24
22
 
25
23
  group :postgresql do
26
- gem "pg", "~> 0.18.0", platforms: [:ruby]
24
+ gem "pg", "~> 1.1.4"
27
25
  end
28
26
 
29
27
  group :mysql do
30
- gem "mysql2", "~> 0.4.10", platforms: [:ruby]
28
+ gem "mysql2", "~> 0.5.0"
31
29
  end
32
30
 
33
31
  gemspec path: "../"