acts_as_list 1.0.4 → 1.2.6

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -4
  3. data/README.md +17 -9
  4. data/Rakefile +0 -8
  5. data/lib/acts_as_list/active_record/acts/callback_definer.rb +2 -4
  6. data/lib/acts_as_list/active_record/acts/list.rb +38 -34
  7. data/lib/acts_as_list/active_record/acts/position_column_method_definer.rb +15 -6
  8. data/lib/acts_as_list/active_record/acts/scope_method_definer.rb +25 -5
  9. data/lib/acts_as_list/active_record/acts/sequential_updates_method_definer.rb +15 -15
  10. data/lib/acts_as_list/active_record/acts/with_connection.rb +25 -0
  11. data/lib/acts_as_list/version.rb +1 -1
  12. metadata +18 -61
  13. data/.github/FUNDING.yml +0 -3
  14. data/.gitignore +0 -12
  15. data/.travis.yml +0 -55
  16. data/Appraisals +0 -40
  17. data/Gemfile +0 -28
  18. data/acts_as_list.gemspec +0 -34
  19. data/gemfiles/rails_4_2.gemfile +0 -32
  20. data/gemfiles/rails_5_0.gemfile +0 -31
  21. data/gemfiles/rails_5_1.gemfile +0 -31
  22. data/gemfiles/rails_5_2.gemfile +0 -31
  23. data/gemfiles/rails_6_0.gemfile +0 -31
  24. data/gemfiles/rails_6_1.gemfile +0 -31
  25. data/test/database.yml +0 -16
  26. data/test/helper.rb +0 -69
  27. data/test/shared.rb +0 -12
  28. data/test/shared_array_scope_list.rb +0 -177
  29. data/test/shared_list.rb +0 -324
  30. data/test/shared_list_sub.rb +0 -188
  31. data/test/shared_no_addition.rb +0 -38
  32. data/test/shared_quoting.rb +0 -23
  33. data/test/shared_top_addition.rb +0 -110
  34. data/test/shared_zero_based.rb +0 -104
  35. data/test/test_default_scope_with_select.rb +0 -33
  36. data/test/test_joined_list.rb +0 -61
  37. data/test/test_list.rb +0 -1086
  38. data/test/test_no_update_for_extra_classes.rb +0 -131
  39. data/test/test_no_update_for_scope_destruction.rb +0 -69
  40. data/test/test_no_update_for_subclasses.rb +0 -56
  41. data/test/test_scope_with_user_defined_foreign_key.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a36b9ed8db57946d5ac96c829c68ce4f4107068628827840140e9dadb3c682fa
4
- data.tar.gz: 60b6890922d26b913aed5a212eae7d228629c1ce3721dd0818ec2e5069697d25
3
+ metadata.gz: 957cacf5cbca5ad0bbc4f543856f54a56cd55e9ec4513bc52467229a92690481
4
+ data.tar.gz: a9917febbae3f5f281ef4ede81bce9843e8224cfb444fd19a2ebc6fcc8a89aad
5
5
  SHA512:
6
- metadata.gz: 1bfced5d2dd4e50194eb8bdb92b244d40dd0a19df0ee663649510a965077a0d332715dc0100c1e9d006222e96e57745638c77812767b88ec7b9274dd350af4c1
7
- data.tar.gz: 485f6be53763f1f112095075b1c69d6e620f89018c2e3435f3500decf986e110c832f78ce5db6462d6233d13816516a2c1d59df1e3c172455439a11704b1c8b5
6
+ metadata.gz: c37e7b4e1ffe907f923072f43d4b587f7a55cf6b34e637b53af8bb73062365aa7328e020b50a1d6c63734dd41bf7a6427fa47485ba5ea5dc2d17806743f383d5
7
+ data.tar.gz: 63c7a691d02b65619e1203fa4eb35316df1e46c4ab8e4e103dcd5e869a762449abac2e31a45671abc4b2291c442bcec8e95f51f56a3a918a22df94de47378e12
data/CHANGELOG.md CHANGED
@@ -6,6 +6,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## v1.2.6 - 2025-10-21
10
+
11
+ - Expose configuration via class-level method (acts_as_list_options) [\#447](https://github.com/brendon/acts_as_list/pull/447) ([anthony0030])
12
+
13
+ ## v1.2.5 - 2025-10-21
14
+
15
+ - Fix crash when deleting an association with composite primary keys [\#450](https://github.com/brendon/acts_as_list/pull/450) ([smathieu])
16
+
17
+ ## v1.2.4 - 2024-11-20
18
+
19
+ ### Added
20
+ - funding_uri to gemspec
21
+
22
+ ## v1.2.3 - 2024-10-14
23
+
24
+ ### Changed
25
+ - Use `.with_connection do |connection|` where possible instead of `.connection` [\#441](https://github.com/brendon/acts_as_list/pull/441) ([flood4life])
26
+
27
+ ## v1.2.2 - 2024-07-16
28
+
29
+ ### Fixed
30
+ - Updated .gemspec to exclude unnecessary files from the gem package. [\#437](https://github.com/brendon/acts_as_list/pull/437) ([f440])
31
+
32
+ ## v1.2.1 - 2024-06-06
33
+
34
+ ### Fixed
35
+ - We no longer delegate the class `connection` method to the instance. [\#436](https://github.com/brendon/acts_as_list/pull/436) ([davidcelis])
36
+
37
+ ## v1.2.0 - 2024-06-03
38
+
39
+ ### Added
40
+ - Add support for composite primary keys. [\#430](https://github.com/brendon/acts_as_list/pull/430) ([divagueame])
41
+
42
+ ### Changed
43
+ - Refactored CI testing framework and removed the Appraisals gem. Use RAILS_VERSION to switch the Rails version you are currently running the tests against.
44
+ - We now only explicitly test against Rails 6.1+ and Ruby 3.0+.
45
+
46
+ ## v1.1.0 - 2023-02-01
47
+
48
+ ### Fixed (Possibly Breaking)
49
+ - 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))
50
+ - 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])
51
+
9
52
  ## v1.0.4 - 2021-04-20
10
53
 
11
54
  ### Fixed
@@ -250,7 +293,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
250
293
  **Closed issues:**
251
294
 
252
295
  - DEPRECATION WARNING on rails 5.0 as of acts\_as\_list 0.9 [\#251](https://github.com/brendon/acts_as_list/issues/251)
253
- - highter\_items returns items with the same position value [\#247](https://github.com/brendon/acts_as_list/issues/247)
296
+ - higher\_items returns items with the same position value [\#247](https://github.com/brendon/acts_as_list/issues/247)
254
297
  - Broken with unique constraint on position [\#245](https://github.com/brendon/acts_as_list/issues/245)
255
298
 
256
299
  **Merged pull requests:**
@@ -478,7 +521,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
478
521
  - Fix sanitize\_sql\_hash\_for\_conditions deprecation warning in Rails 4.2 [\#140](https://github.com/brendon/acts_as_list/pull/140) ([eagletmt](https://github.com/eagletmt))
479
522
  - Simpler method to find the subclass name [\#139](https://github.com/brendon/acts_as_list/pull/139) ([brendon](https://github.com/brendon))
480
523
  - Rails4 enum column support [\#130](https://github.com/brendon/acts_as_list/pull/130) ([arunagw](https://github.com/arunagw))
481
- - use eval for determing the self.class.name useful when this is used in an abstract class [\#123](https://github.com/brendon/acts_as_list/pull/123) ([flarik](https://github.com/flarik))
524
+ - use eval for determining the self.class.name useful when this is used in an abstract class [\#123](https://github.com/brendon/acts_as_list/pull/123) ([flarik](https://github.com/flarik))
482
525
 
483
526
  ## [0.5.0](https://github.com/brendon/acts_as_list/tree/0.5.0) (2014-10-31)
484
527
  [Full Changelog](https://github.com/brendon/acts_as_list/compare/0.4.0...0.5.0)
@@ -618,7 +661,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
618
661
  - acts\_as\_list :scope =\> "doesnt\_seem\_to\_work" [\#12](https://github.com/brendon/acts_as_list/issues/12)
619
662
  - don't work perfectly with default\_scope [\#11](https://github.com/brendon/acts_as_list/issues/11)
620
663
  - MySQL: Position column MUST NOT have default [\#10](https://github.com/brendon/acts_as_list/issues/10)
621
- - insert\_at fails on postgresql w/ non-null constraint on postion\_column [\#8](https://github.com/brendon/acts_as_list/issues/8)
664
+ - insert\_at fails on postgresql w/ non-null constraint on position\_column [\#8](https://github.com/brendon/acts_as_list/issues/8)
622
665
 
623
666
  **Merged pull requests:**
624
667
 
@@ -627,7 +670,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
627
670
  - Massive test refactorings. [\#24](https://github.com/brendon/acts_as_list/pull/24) ([splattael](https://github.com/splattael))
628
671
  - Silent migrations to reduce test noise. [\#22](https://github.com/brendon/acts_as_list/pull/22) ([splattael](https://github.com/splattael))
629
672
  - Should decrement lower items after the item has been destroyed to avoid unique key conflicts. [\#18](https://github.com/brendon/acts_as_list/pull/18) ([aepstein](https://github.com/aepstein))
630
- - Fix spelling and grammer [\#15](https://github.com/brendon/acts_as_list/pull/15) ([tmiller](https://github.com/tmiller))
673
+ - Fix spelling and grammar [\#15](https://github.com/brendon/acts_as_list/pull/15) ([tmiller](https://github.com/tmiller))
631
674
  - store\_at\_0 should yank item from the list then decrement items to avoid r [\#14](https://github.com/brendon/acts_as_list/pull/14) ([aepstein](https://github.com/aepstein))
632
675
  - Support default\_scope ordering by calling .unscoped [\#13](https://github.com/brendon/acts_as_list/pull/13) ([tanordheim](https://github.com/tanordheim))
633
676
 
data/README.md CHANGED
@@ -1,9 +1,14 @@
1
1
  # Acts As List
2
2
 
3
3
  ## Build Status
4
- [![Build Status](https://travis-ci.org/brendon/acts_as_list.svg?branch=master)](https://travis-ci.org/brendon/acts_as_list)
4
+ [![Continuous Integration](https://github.com/brendon/acts_as_list/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/brendon/acts_as_list/actions/workflows/continuous_integration.yml)
5
5
  [![Gem Version](https://badge.fury.io/rb/acts_as_list.svg)](https://badge.fury.io/rb/acts_as_list)
6
6
 
7
+ ## ANNOUNCING: Positioning, the gem
8
+ As maintainer of both Acts As List and the Ranked Model gems, I've become intimately acquainted with the strengths and weaknesses of each. I ended up writing a small scale Rails Concern for positioning database rows for a recent project and it worked really well so I've decided to release it as a gem: [Positioning](https://github.com/brendon/positioning)
9
+
10
+ Positioning works similarly to Acts As List in that it maintains a sequential list of integer values as positions. It differs in that it encourages a unique constraints on the position column and supports multiple lists per database table. It borrows Ranked Model's concept of relative positioning. I encourage you to check it out and give it a whirl on your project!
11
+
7
12
  ## Description
8
13
 
9
14
  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.
@@ -117,7 +122,7 @@ When using PostgreSQL, it is also possible to leave this migration up to the dat
117
122
  ROW_NUMBER() OVER (
118
123
  PARTITION BY todo_list_id
119
124
  ORDER BY updated_at
120
- ) as new_position
125
+ ) AS new_position
121
126
  FROM todo_items
122
127
  ) AS mapping
123
128
  WHERE todo_items.id = mapping.id;
@@ -133,26 +138,29 @@ The `position` column is set after validations are called, so you should not put
133
138
  If you need a scope by a non-association field you should pass an array, containing field name, to a scope:
134
139
  ```ruby
135
140
  class TodoItem < ActiveRecord::Base
136
- # `kind` is a plain text field (e.g. 'work', 'shopping', 'meeting'), not an association
137
- acts_as_list scope: [:kind]
141
+ # `task_category` is a plain text field (e.g. 'work', 'shopping', 'meeting'), not an association
142
+ acts_as_list scope: [:task_category]
138
143
  end
139
144
  ```
140
145
 
141
146
  You can also add multiple scopes in this fashion:
142
147
  ```ruby
143
148
  class TodoItem < ActiveRecord::Base
144
- acts_as_list scope: [:kind, :owner_id]
149
+ belongs_to :todo_list
150
+ acts_as_list scope: [:task_category, :todo_list_id]
145
151
  end
146
152
  ```
147
153
 
148
154
  Furthermore, you can optionally include a hash of fixed parameters that will be included in all queries:
149
155
  ```ruby
150
156
  class TodoItem < ActiveRecord::Base
151
- acts_as_list scope: [:kind, :owner_id, deleted_at: nil]
157
+ belongs_to :todo_list
158
+ # or `discarded_at` if using discard
159
+ acts_as_list scope: [:task_category, :todo_list_id, deleted_at: nil]
152
160
  end
153
161
  ```
154
162
 
155
- This is useful when using this gem in conjunction with the popular [acts_as_paranoid](https://github.com/ActsAsParanoid/acts_as_paranoid) gem.
163
+ 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.
156
164
 
157
165
  ## More Options
158
166
  - `column`
@@ -192,7 +200,7 @@ class TodoItem < ActiveRecord::Base
192
200
  end
193
201
 
194
202
  class TodoAttachment < ActiveRecord::Base
195
- belongs_to :todo_list
203
+ belongs_to :todo_item
196
204
  acts_as_list scope: :todo_item
197
205
  end
198
206
 
@@ -282,7 +290,7 @@ All versions `0.1.5` onwards require Rails 3.0.x and higher.
282
290
 
283
291
  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:
284
292
 
285
- 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]`. 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).
293
+ 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]`. 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 property 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).
286
294
  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.
287
295
 
288
296
  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.
data/Rakefile CHANGED
@@ -32,11 +32,3 @@ rescue LoadError
32
32
  rescue StandardError
33
33
  puts "RDocTask is not supported on this platform."
34
34
  end
35
-
36
- # See https://github.com/skywinder/github-changelog-generator#rake-task for details
37
- # and github_changelog_generator --help for available options
38
- require 'github_changelog_generator/task'
39
- GitHubChangelogGenerator::RakeTask.new :changelog do |config|
40
- config.project = 'acts_as_list'
41
- config.user = 'brendon'
42
- end
@@ -11,11 +11,9 @@ module ActiveRecord::Acts::List::CallbackDefiner #:nodoc:
11
11
  before_update :check_scope, unless: :act_as_list_no_update?
12
12
  after_update :update_positions, unless: :act_as_list_no_update?
13
13
 
14
- after_commit :clear_scope_changed
14
+ after_save :clear_scope_changed
15
15
 
16
- if add_new_at.present?
17
- before_create "add_to_list_#{add_new_at}".to_sym, unless: :act_as_list_no_update?
18
- end
16
+ before_create :avoid_collision, unless: :act_as_list_no_update?
19
17
  end
20
18
  end
21
19
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './with_connection'
4
+
3
5
  module ActiveRecord
4
6
  module Acts #:nodoc:
5
7
  module List #:nodoc:
@@ -25,6 +27,11 @@ module ActiveRecord
25
27
  configuration = { column: "position", scope: "1 = 1", top_of_list: 1, add_new_at: :bottom, touch_on_update: true }
26
28
  configuration.update(options) if options.is_a?(Hash)
27
29
 
30
+ # * Expose configuration via class-level method
31
+ define_singleton_method(:acts_as_list_options) do
32
+ configuration.dup.freeze
33
+ end
34
+
28
35
  caller_class = self
29
36
 
30
37
  ActiveRecord::Acts::List::PositionColumnMethodDefiner.call(caller_class, configuration[:column], configuration[:touch_on_update])
@@ -171,7 +178,7 @@ module ActiveRecord
171
178
  limit ||= acts_as_list_list.count
172
179
  acts_as_list_list.
173
180
  where("#{quoted_position_column_with_table_name} <= ?", current_position).
174
- where("#{quoted_table_name}.#{self.class.primary_key} != ?", self.send(self.class.primary_key)).
181
+ where.not(primary_key_condition).
175
182
  reorder(acts_as_list_order_argument(:desc)).
176
183
  limit(limit)
177
184
  end
@@ -188,7 +195,7 @@ module ActiveRecord
188
195
  limit ||= acts_as_list_list.count
189
196
  acts_as_list_list.
190
197
  where("#{quoted_position_column_with_table_name} >= ?", current_position).
191
- where("#{quoted_table_name}.#{self.class.primary_key} != ?", self.send(self.class.primary_key)).
198
+ where.not(primary_key_condition).
192
199
  reorder(acts_as_list_order_argument(:asc)).
193
200
  limit(limit)
194
201
  end
@@ -229,36 +236,27 @@ module ActiveRecord
229
236
  acts_as_list_class.default_scoped.unscope(:select, :where).where(scope_condition)
230
237
  end
231
238
 
232
- # Poorly named methods. They will insert the item at the desired position if the position
233
- # has been set manually using position=, not necessarily the top or bottom of the list:
234
-
235
- def add_to_list_top
236
- if assume_default_position?
237
- increment_positions_on_all_items
238
- self[position_column] = acts_as_list_top
239
- else
240
- increment_positions_on_lower_items(self[position_column], id)
241
- end
242
-
243
- # Make sure we know that we've processed this scope change already
244
- @scope_changed = false
245
-
246
- # Don't halt the callback chain
247
- true
248
- end
249
-
250
- def add_to_list_bottom
251
- if assume_default_position?
252
- self[position_column] = bottom_position_in_list.to_i + 1
239
+ def avoid_collision
240
+ case add_new_at
241
+ when :top
242
+ if assume_default_position?
243
+ increment_positions_on_all_items
244
+ self[position_column] = acts_as_list_top
245
+ else
246
+ increment_positions_on_lower_items(self[position_column], id)
247
+ end
248
+ when :bottom
249
+ if assume_default_position?
250
+ self[position_column] = bottom_position_in_list.to_i + 1
251
+ else
252
+ increment_positions_on_lower_items(self[position_column], id)
253
+ end
253
254
  else
254
- increment_positions_on_lower_items(self[position_column], id)
255
+ increment_positions_on_lower_items(self[position_column], id) if position_changed
255
256
  end
256
257
 
257
- # Make sure we know that we've processed this scope change already
258
- @scope_changed = false
259
-
260
- # Don't halt the callback chain
261
- true
258
+ @scope_changed = false # Make sure we know that we've processed this scope change already
259
+ return true # Don't halt the callback chain
262
260
  end
263
261
 
264
262
  def assume_default_position?
@@ -282,7 +280,7 @@ module ActiveRecord
282
280
  scope = acts_as_list_list
283
281
 
284
282
  if except
285
- scope = scope.where("#{quoted_table_name}.#{self.class.primary_key} != ?", except.id)
283
+ scope = scope.where.not(primary_key_condition(except.id))
286
284
  end
287
285
 
288
286
  scope.in_list.reorder(acts_as_list_order_argument(:desc)).first
@@ -309,7 +307,7 @@ module ActiveRecord
309
307
  scope = acts_as_list_list
310
308
 
311
309
  if avoid_id
312
- scope = scope.where("#{quoted_table_name}.#{self.class.primary_key} != ?", avoid_id)
310
+ scope = scope.where.not(primary_key_condition(avoid_id))
313
311
  end
314
312
 
315
313
  if sequential_updates?
@@ -350,7 +348,7 @@ module ActiveRecord
350
348
  scope = acts_as_list_list
351
349
 
352
350
  if avoid_id
353
- scope = scope.where("#{quoted_table_name}.#{self.class.primary_key} != ?", avoid_id)
351
+ scope = scope.where.not(primary_key_condition(avoid_id))
354
352
  end
355
353
 
356
354
  if old_position < new_position
@@ -454,7 +452,7 @@ module ActiveRecord
454
452
  send('decrement_positions_on_lower_items') if lower_item
455
453
  cached_changes.each { |attribute, values| send("#{attribute}=", values[1]) }
456
454
 
457
- send("add_to_list_#{add_new_at}") if add_new_at.present?
455
+ avoid_collision
458
456
  end
459
457
  end
460
458
 
@@ -468,7 +466,9 @@ module ActiveRecord
468
466
 
469
467
  # When using raw column name it must be quoted otherwise it can raise syntax errors with SQL keywords (e.g. order)
470
468
  def quoted_position_column
471
- @_quoted_position_column ||= self.class.connection.quote_column_name(position_column)
469
+ @_quoted_position_column ||= ActiveRecord::Acts::List::WithConnection.new(self.class).call do |connection|
470
+ connection.quote_column_name(position_column)
471
+ end
472
472
  end
473
473
 
474
474
  # Used in order clauses
@@ -489,6 +489,10 @@ module ActiveRecord
489
489
  version = Gem.loaded_specs['activerecord'].version
490
490
  requirement.satisfied_by?(version)
491
491
  end
492
+
493
+ def primary_key_condition(id = self.id)
494
+ { self.class.primary_key => [id] }
495
+ end
492
496
  end
493
497
 
494
498
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './with_connection'
4
+
3
5
  module ActiveRecord::Acts::List::PositionColumnMethodDefiner #:nodoc:
4
6
  def self.call(caller_class, position_column, touch_on_update)
5
7
  define_class_methods(caller_class, position_column, touch_on_update)
@@ -15,7 +17,9 @@ module ActiveRecord::Acts::List::PositionColumnMethodDefiner #:nodoc:
15
17
  def self.define_class_methods(caller_class, position_column, touch_on_update)
16
18
  caller_class.class_eval do
17
19
  define_singleton_method :quoted_position_column do
18
- @_quoted_position_column ||= connection.quote_column_name(position_column)
20
+ @_quoted_position_column ||= ActiveRecord::Acts::List::WithConnection.new(self).call do |connection|
21
+ connection.quote_column_name(position_column)
22
+ end
19
23
  end
20
24
 
21
25
  define_singleton_method :quoted_position_column_with_table_name do
@@ -72,17 +76,22 @@ module ActiveRecord::Acts::List::PositionColumnMethodDefiner #:nodoc:
72
76
  cached_quoted_now = quoted_current_time_from_proper_timezone
73
77
 
74
78
  timestamp_attributes_for_update_in_model.map do |attr|
75
- ", #{connection.quote_column_name(attr)} = #{cached_quoted_now}"
79
+ ActiveRecord::Acts::List::WithConnection.new(self.class).call do |connection|
80
+ ", #{connection.quote_column_name(attr)} = #{cached_quoted_now}"
81
+ end
76
82
  end.join
77
83
  end
78
84
 
79
85
  private
80
86
 
81
- delegate :connection, to: self
82
-
83
87
  def quoted_current_time_from_proper_timezone
84
- connection.quote(connection.quoted_date(
85
- current_time_from_proper_timezone))
88
+ ActiveRecord::Acts::List::WithConnection.new(self.class).call do |connection|
89
+ connection.quote(
90
+ connection.quoted_date(
91
+ current_time_from_proper_timezone
92
+ )
93
+ )
94
+ end
86
95
  end
87
96
  end
88
97
  end
@@ -2,7 +2,6 @@
2
2
  require "active_support/inflector"
3
3
 
4
4
  module ActiveRecord::Acts::List::ScopeMethodDefiner #:nodoc:
5
- extend ActiveSupport::Inflector
6
5
 
7
6
  def self.call(caller_class, scope)
8
7
  scope = idify(caller_class, scope) if scope.is_a?(Symbol)
@@ -22,7 +21,16 @@ module ActiveRecord::Acts::List::ScopeMethodDefiner #:nodoc:
22
21
  end
23
22
 
24
23
  define_method :destroyed_via_scope? do
25
- scope == (destroyed_by_association && destroyed_by_association.foreign_key.to_sym)
24
+ return false unless destroyed_by_association
25
+
26
+ foreign_key = destroyed_by_association.foreign_key
27
+ if foreign_key.is_a?(Array)
28
+ # Composite foreign key - check if scope is one of the keys
29
+ foreign_key.map(&:to_sym).include?(scope)
30
+ else
31
+ # Single foreign key
32
+ scope == foreign_key.to_sym
33
+ end
26
34
  end
27
35
  elsif scope.is_a?(Array)
28
36
  define_method :scope_condition do
@@ -45,7 +53,16 @@ module ActiveRecord::Acts::List::ScopeMethodDefiner #:nodoc:
45
53
  end
46
54
 
47
55
  define_method :destroyed_via_scope? do
48
- scope_condition.keys.include? (destroyed_by_association && destroyed_by_association.foreign_key.to_sym)
56
+ return false unless destroyed_by_association
57
+
58
+ foreign_key = destroyed_by_association.foreign_key
59
+ if foreign_key.is_a?(Array)
60
+ # Composite foreign key - check if any keys overlap with scope
61
+ (scope_condition.keys & foreign_key.map(&:to_sym)).any?
62
+ else
63
+ # Single foreign key
64
+ scope_condition.keys.include?(foreign_key.to_sym)
65
+ end
49
66
  end
50
67
  else
51
68
  define_method :scope_condition do
@@ -69,9 +86,12 @@ module ActiveRecord::Acts::List::ScopeMethodDefiner #:nodoc:
69
86
  return name if name.to_s =~ /_id$/
70
87
 
71
88
  if caller_class.reflections.key?(name.to_s)
72
- caller_class.reflections[name.to_s].foreign_key.to_sym
89
+ foreign_key = caller_class.reflections[name.to_s].foreign_key
90
+ # Handle composite foreign keys (Arrays) by returning as-is
91
+ # Single foreign keys should be converted to symbols
92
+ foreign_key.is_a?(Array) ? foreign_key.map(&:to_sym) : foreign_key.to_sym
73
93
  else
74
- foreign_key(name).to_sym
94
+ ActiveSupport::Inflector.foreign_key(name).to_sym
75
95
  end
76
96
  end
77
97
  end
@@ -1,24 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './with_connection'
4
+
3
5
  module ActiveRecord::Acts::List::SequentialUpdatesMethodDefiner #:nodoc:
4
6
  def self.call(caller_class, column, sequential_updates_option)
5
7
  caller_class.class_eval do
6
8
  define_method :sequential_updates? do
7
- if !defined?(@sequential_updates)
8
- if sequential_updates_option.nil?
9
- table_exists =
10
- if active_record_version_is?('>= 5')
11
- caller_class.connection.data_source_exists?(caller_class.table_name)
12
- else
13
- caller_class.connection.table_exists?(caller_class.table_name)
14
- end
15
- index_exists = caller_class.connection.index_exists?(caller_class.table_name, column, unique: true)
16
- @sequential_updates = table_exists && index_exists
17
- else
18
- @sequential_updates = sequential_updates_option
19
- end
20
- else
21
- @sequential_updates
9
+ return @sequential_updates if defined?(@sequential_updates)
10
+
11
+ return @sequential_updates = sequential_updates_option unless sequential_updates_option.nil?
12
+
13
+ ActiveRecord::Acts::List::WithConnection.new(caller_class).call do |connection|
14
+ table_exists =
15
+ if active_record_version_is?('>= 5')
16
+ connection.data_source_exists?(caller_class.table_name)
17
+ else
18
+ connection.table_exists?(caller_class.table_name)
19
+ end
20
+ index_exists = connection.index_exists?(caller_class.table_name, column, unique: true)
21
+ @sequential_updates = table_exists && index_exists
22
22
  end
23
23
  end
24
24
 
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Acts
5
+ module List
6
+ class WithConnection
7
+ def initialize(recipient)
8
+ @recipient = recipient
9
+ end
10
+
11
+ attr_reader :recipient
12
+
13
+ def call
14
+ if recipient.respond_to?(:with_connection)
15
+ recipient.with_connection do |connection|
16
+ yield connection
17
+ end
18
+ else
19
+ yield recipient.connection
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Acts
5
5
  module List
6
- VERSION = '1.0.4'
6
+ VERSION = '1.2.6'
7
7
  end
8
8
  end
9
9
  end