New Upstream Release - ruby-acts-as-list

Ready changes

Summary

Merged new upstream version: 1.1.0 (was: 1.0.4).

Resulting package

Built on 2023-02-24T23:40 (took 2m13s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-acts-as-list

Lintian Result

Diff

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..5ace460
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..c905b07
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,123 @@
+---
+name: CI
+on: [push, pull_request]
+
+jobs:
+  tests:
+    name: Ruby ${{ matrix.ruby }}, ${{ matrix.gemfile }}, DB ${{ matrix.db }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        ruby:
+          - 2.4
+          - 2.5
+          - 2.6
+          - 2.7
+          - '3.0'
+          - 3.1
+          - 3.2
+        gemfile:
+          - gemfiles/rails_4_2.gemfile
+          - gemfiles/rails_5_0.gemfile
+          - gemfiles/rails_5_1.gemfile
+          - gemfiles/rails_5_2.gemfile
+          - gemfiles/rails_6_0.gemfile
+          - gemfiles/rails_6_1.gemfile
+          - gemfiles/rails_7_0.gemfile
+        db:
+          - sqlite
+          - mysql
+          - postgresql
+        exclude:
+          - ruby: 2.4
+            gemfile: gemfiles/rails_6_0.gemfile
+          - ruby: 2.4
+            gemfile: gemfiles/rails_6_1.gemfile
+          - ruby: 2.4
+            gemfile: gemfiles/rails_7_0.gemfile
+          - ruby: 2.5
+            gemfile: gemfiles/rails_7_0.gemfile
+          - ruby: 2.6
+            gemfile: gemfiles/rails_7_0.gemfile
+          - ruby: 2.7
+            gemfile: gemfiles/rails_4_2.gemfile
+          - ruby: 2.7
+            gemfile: gemfiles/rails_5_0.gemfile
+            db: sqlite
+          - ruby: '3.0'
+            gemfile: gemfiles/rails_4_2.gemfile
+          - ruby: '3.0'
+            gemfile: gemfiles/rails_5_0.gemfile
+          - ruby: '3.0'
+            gemfile: gemfiles/rails_5_1.gemfile
+          - ruby: '3.0'
+            gemfile: gemfiles/rails_5_2.gemfile
+          - ruby: 3.1
+            gemfile: gemfiles/rails_4_2.gemfile
+          - ruby: 3.1
+            gemfile: gemfiles/rails_5_0.gemfile
+          - ruby: 3.1
+            gemfile: gemfiles/rails_5_1.gemfile
+          - ruby: 3.1
+            gemfile: gemfiles/rails_5_2.gemfile
+          - ruby: 3.1
+            gemfile: gemfiles/rails_6_0.gemfile
+          - ruby: 3.2
+            gemfile: gemfiles/rails_4_2.gemfile
+          - ruby: 3.2
+            gemfile: gemfiles/rails_5_0.gemfile
+          - ruby: 3.2
+            gemfile: gemfiles/rails_5_1.gemfile
+          - ruby: 3.2
+            gemfile: gemfiles/rails_5_2.gemfile
+          - ruby: 3.2
+            gemfile: gemfiles/rails_6_0.gemfile
+          - ruby: 3.2
+            gemfile: gemfiles/rails_6_1.gemfile
+        os:
+          - ubuntu-latest
+    services:
+      mysql:
+        image: mysql:5.7
+        env:
+            MYSQL_ALLOW_EMPTY_PASSWORD: yes
+        ports:
+            - 3306:3306
+        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+
+      postgres:
+        # Docker Hub image
+        image: postgres
+        # Provide the password for postgres
+        env:
+          POSTGRES_USER: postgres
+          POSTGRES_HOST_AUTH_METHOD: trust
+        ports:
+          - 5432:5432
+        # Set health checks to wait until postgres has started
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+    env:
+      BUNDLE_GEMFILE: ${{ matrix.gemfile }}
+      DB: ${{ matrix.db }}
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Ruby
+        uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: ${{ matrix.ruby }}
+          bundler-cache: true
+      - name: "Create MySQL database"
+        if: ${{ env.DB == 'mysql' }}
+        run: |
+          mysql -h 127.0.0.1 -u root -e 'create database acts_as_list;'
+      - name: "Create PostgreSQL database"
+        if: ${{ env.DB == 'postgresql' }}
+        run: |
+          psql -c 'create database acts_as_list;' -h localhost -U postgres
+      - name: Run tests
+        run: bundle exec rake 
diff --git a/Appraisals b/Appraisals
index e94ebef..4b48737 100644
--- a/Appraisals
+++ b/Appraisals
@@ -36,5 +36,9 @@ appraise "rails-6-0" do
 end
 
 appraise "rails-6-1" do
-  gem "activerecord", "6.1.0.rc1"
+  gem "activerecord", "~> 6.1.0"
+end
+
+appraise "rails-7-0" do
+  gem "activerecord", "~> 7.0.0"
 end
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 271741a..9ebf741 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## Unreleased
 
+## v1.1.0 - 2023-02-01
+
+### Fixed (Possibly Breaking)
+- Use `after_save` instead of `after_commit` for `clear_scope_changed` callback [\#407](https://github.com/brendon/acts_as_list/pull/407) ([@Flixt](https://github.com/Flixt))
+- Rename `add_to_list_top` and `add_to_list_bottom` private methods to `avoid_collision` that handles both cases as well as the case where `:add_new_at` is `nil`. Setting an explicit position when `:add_new_at` is `nil` will now shuffle other items out of the way if necessary. *This may break existing workarounds you have in place to deal with this bug*. [\#411](https://github.com/brendon/acts_as_list/pull/411). ([brendon])
+
 ## v1.0.4 - 2021-04-20
 
 ### Fixed
diff --git a/README.md b/README.md
index 651ab59..44a1192 100644
--- a/README.md
+++ b/README.md
@@ -133,26 +133,29 @@ The `position` column is set after validations are called, so you should not put
 If you need a scope by a non-association field you should pass an array, containing field name, to a scope:
 ```ruby
 class TodoItem < ActiveRecord::Base
-  # `kind` is a plain text field (e.g. 'work', 'shopping', 'meeting'), not an association
-  acts_as_list scope: [:kind]
+  # `task_category` is a plain text field (e.g. 'work', 'shopping', 'meeting'), not an association
+  acts_as_list scope: [:task_category]
 end
 ```
 
 You can also add multiple scopes in this fashion:
 ```ruby
 class TodoItem < ActiveRecord::Base
-  acts_as_list scope: [:kind, :owner_id]
+  belongs_to :todo_list
+  acts_as_list scope: [:task_category, :todo_list_id]
 end
 ```
 
 Furthermore, you can optionally include a hash of fixed parameters that will be included in all queries:
 ```ruby
 class TodoItem < ActiveRecord::Base
-  acts_as_list scope: [:kind, :owner_id, deleted_at: nil]
+  belongs_to :todo_list
+  # or `discarded_at` if using discard
+  acts_as_list scope: [:task_category, :todo_list_id, deleted_at: nil]
 end
 ```
 
-This is useful when using this gem in conjunction with the popular [acts_as_paranoid](https://github.com/ActsAsParanoid/acts_as_paranoid) gem.
+This is useful when using this gem in conjunction with the popular [acts_as_paranoid](https://github.com/ActsAsParanoid/acts_as_paranoid) or [discard](https://github.com/jhawthorn/discard) gems.
 
 ## More Options
 - `column`
diff --git a/debian/changelog b/debian/changelog
index 0e3b5c5..2b9e874 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ruby-acts-as-list (1.1.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 24 Feb 2023 23:38:44 -0000
+
 ruby-acts-as-list (1.0.4-1) unstable; urgency=medium
 
   [ Andrius Merkys ]
diff --git a/debian/patches/0001-Remove-bundler-setup-from-tests.patch b/debian/patches/0001-Remove-bundler-setup-from-tests.patch
index 77a1e36..574ec12 100644
--- a/debian/patches/0001-Remove-bundler-setup-from-tests.patch
+++ b/debian/patches/0001-Remove-bundler-setup-from-tests.patch
@@ -6,10 +6,10 @@ Subject: Remove bundler setup from tests
  test/helper.rb | 11 +----------
  1 file changed, 1 insertion(+), 10 deletions(-)
 
-diff --git a/test/helper.rb b/test/helper.rb
-index 0a23919..f0f8faa 100644
---- a/test/helper.rb
-+++ b/test/helper.rb
+Index: ruby-acts-as-list.git/test/helper.rb
+===================================================================
+--- ruby-acts-as-list.git.orig/test/helper.rb
++++ ruby-acts-as-list.git/test/helper.rb
 @@ -2,19 +2,10 @@
  
  # $DEBUG = true
diff --git a/gemfiles/rails_6_1.gemfile b/gemfiles/rails_7_0.gemfile
similarity index 77%
rename from gemfiles/rails_6_1.gemfile
rename to gemfiles/rails_7_0.gemfile
index 9b2f40d..03bf15e 100644
--- a/gemfiles/rails_6_1.gemfile
+++ b/gemfiles/rails_7_0.gemfile
@@ -4,10 +4,10 @@ source "http://rubygems.org"
 
 gem "rake"
 gem "appraisal"
-gem "activerecord", "6.1.0.rc1"
+gem "activerecord", "~> 7.0.0"
 
 group :development do
-  gem "github_changelog_generator", "1.9.0"
+  gem "github_changelog_generator", "~> 1.16.0"
 end
 
 group :test do
@@ -21,7 +21,7 @@ group :sqlite do
 end
 
 group :postgresql do
-  gem "pg", "~> 1.2.0"
+  gem "pg", "~> 1.3.0"
 end
 
 group :mysql do
diff --git a/lib/acts_as_list/active_record/acts/callback_definer.rb b/lib/acts_as_list/active_record/acts/callback_definer.rb
index 5e7e53a..9769e49 100644
--- a/lib/acts_as_list/active_record/acts/callback_definer.rb
+++ b/lib/acts_as_list/active_record/acts/callback_definer.rb
@@ -11,11 +11,9 @@ module ActiveRecord::Acts::List::CallbackDefiner #:nodoc:
       before_update :check_scope, unless: :act_as_list_no_update?
       after_update :update_positions, unless: :act_as_list_no_update?
 
-      after_commit :clear_scope_changed
+      after_save :clear_scope_changed
 
-      if add_new_at.present?
-        before_create "add_to_list_#{add_new_at}".to_sym, unless: :act_as_list_no_update?
-      end
+      before_create :avoid_collision, unless: :act_as_list_no_update?
     end
   end
 end
diff --git a/lib/acts_as_list/active_record/acts/list.rb b/lib/acts_as_list/active_record/acts/list.rb
index 90294ac..094fb55 100644
--- a/lib/acts_as_list/active_record/acts/list.rb
+++ b/lib/acts_as_list/active_record/acts/list.rb
@@ -229,36 +229,27 @@ module ActiveRecord
           acts_as_list_class.default_scoped.unscope(:select, :where).where(scope_condition)
         end
 
-        # Poorly named methods. They will insert the item at the desired position if the position
-        # has been set manually using position=, not necessarily the top or bottom of the list:
-
-        def add_to_list_top
-          if assume_default_position?
-            increment_positions_on_all_items
-            self[position_column] = acts_as_list_top
-          else
-            increment_positions_on_lower_items(self[position_column], id)
-          end
-
-          # Make sure we know that we've processed this scope change already
-          @scope_changed = false
-
-          # Don't halt the callback chain
-          true
-        end
-
-        def add_to_list_bottom
-          if assume_default_position?
-            self[position_column] = bottom_position_in_list.to_i + 1
+        def avoid_collision
+          case add_new_at
+          when :top
+            if assume_default_position?
+              increment_positions_on_all_items
+              self[position_column] = acts_as_list_top
+            else
+              increment_positions_on_lower_items(self[position_column], id)
+            end
+          when :bottom
+            if assume_default_position?
+              self[position_column] = bottom_position_in_list.to_i + 1
+            else
+              increment_positions_on_lower_items(self[position_column], id)
+            end
           else
-            increment_positions_on_lower_items(self[position_column], id)
+            increment_positions_on_lower_items(self[position_column], id) if position_changed
           end
 
-          # Make sure we know that we've processed this scope change already
-          @scope_changed = false
-
-          # Don't halt the callback chain
-          true
+          @scope_changed = false # Make sure we know that we've processed this scope change already
+          return true # Don't halt the callback chain
         end
 
         def assume_default_position?
@@ -454,7 +445,7 @@ module ActiveRecord
             send('decrement_positions_on_lower_items') if lower_item
             cached_changes.each { |attribute, values| send("#{attribute}=", values[1]) }
 
-            send("add_to_list_#{add_new_at}") if add_new_at.present?
+            avoid_collision
           end
         end
 
diff --git a/lib/acts_as_list/version.rb b/lib/acts_as_list/version.rb
index d055bcb..bae8f36 100644
--- a/lib/acts_as_list/version.rb
+++ b/lib/acts_as_list/version.rb
@@ -3,7 +3,7 @@
 module ActiveRecord
   module Acts
     module List
-      VERSION = '1.0.4'
+      VERSION = '1.1.0'
     end
   end
 end
diff --git a/test/database.yml b/test/database.yml
index 8c4cfe5..b15954f 100644
--- a/test/database.yml
+++ b/test/database.yml
@@ -4,13 +4,15 @@ sqlite:
 
 mysql:
   adapter: mysql2
+  host: 127.0.0.1
   username: root
   password:
   database: acts_as_list
 
 postgresql:
   adapter: postgresql
+  host: localhost
   username: postgres
-  password:
+  password: postgres
   database: acts_as_list
   min_messages: ERROR
diff --git a/test/shared_no_addition.rb b/test/shared_no_addition.rb
index 4d9b294..f80c4db 100644
--- a/test/shared_no_addition.rb
+++ b/test/shared_no_addition.rb
@@ -34,5 +34,40 @@ module Shared
       new.reload
       assert !new.in_list?
     end
+
+    def test_collision_avoidance_with_explicit_position
+      first = NoAdditionMixin.create(parent_id: 20, pos: 1)
+      second = NoAdditionMixin.create(parent_id: 20, pos: 1)
+      third = NoAdditionMixin.create(parent_id: 30, pos: 1)
+
+      first.reload
+      second.reload
+      third.reload
+
+      assert_equal 2, first.pos
+      assert_equal 1, second.pos
+      assert_equal 1, third.pos
+
+      first.update(pos: 1)
+
+      first.reload
+      second.reload
+
+      assert_equal 1, first.pos
+      assert_equal 2, second.pos
+
+      first.update(parent_id: 30)
+
+      first.reload
+      second.reload
+      third.reload
+
+      assert_equal 1, first.pos
+      assert_equal 30, first.parent_id
+      assert_equal 1, second.pos
+      assert_equal 20, second.parent_id
+      assert_equal 2, third.pos
+      assert_equal 30, third.parent_id
+    end
   end
 end
diff --git a/test/test_list.rb b/test/test_list.rb
index 8a3d313..11148f3 100644
--- a/test/test_list.rb
+++ b/test/test_list.rb
@@ -604,6 +604,42 @@ class MultiDestroyTest < ActsAsListTestCase
   end
 end
 
+class MultiUpdateTest < ActsAsListTestCase
+
+  def setup
+    setup_db
+  end
+
+  def test_multiple_updates_within_transaction
+    @page = ListMixin.create! id: 100, parent_id: nil, pos: 1
+    @row = ListMixin.create! parent_id: @page.id, pos: 1
+    @column1 = ListMixin.create! parent_id: @row.id, pos: 1
+    @column2 = ListMixin.create! parent_id: @row.id, pos: 2
+    @rich_text1 = ListMixin.create! parent_id: @column1.id, pos: 1
+    @rich_text2 = ListMixin.create! parent_id: @column2.id, pos: 1
+
+    ActiveRecord::Base.transaction do
+      @rich_text1.update!(parent_id: @column2.id, pos: 1)
+
+      assert_equal [@rich_text1.id, @rich_text2.id], ListMixin.where(parent_id: @column2.id).order('pos').map(&:id)
+      assert_equal [1, 2], ListMixin.where(parent_id: @column2.id).order('pos').map(&:pos)
+
+      @column1.destroy!
+      assert_equal [@column2.id], ListMixin.where(parent_id: @row.id).order('pos').map(&:id)
+      assert_equal [1], ListMixin.where(parent_id: @row.id).order('pos').map(&:pos)
+
+      @rich_text1.update!(parent_id: @page.id, pos: 1)
+      @rich_text2.update!(parent_id: @page.id, pos: 2)
+      @row.destroy!
+      @column2.destroy!
+    end
+
+    assert_equal(1, @page.reload.pos)
+    assert_equal [@rich_text1.id, @rich_text2.id], ListMixin.where(parent_id: @page.id).order('pos').map(&:id)
+    assert_equal [1, 2], ListMixin.where(parent_id: @page.id).order('pos').map(&:pos)
+  end
+end
+
 #class TopAdditionMixin < Mixin
 
 class TopAdditionTest < ActsAsListTestCase

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/acts_as_list-1.1.0.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/acts_as_list-1.0.4.gemspec

No differences were encountered in the control files

More details

Full run details