closure_tree 9.3.0 → 9.5.0

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,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7af281d62397e19768240a93ab284f2ceb9b261f5c4c3d2ac7d288300212b6e
4
- data.tar.gz: ef0cf220eb02fcfafb17034265ee6d3ceb514c13898bf8c71eab1915f2a156c6
3
+ metadata.gz: 6ab4fbc86c1ae73dc78bb959fa0038e88dfa60e91b1f7322707c76bc5bd8583a
4
+ data.tar.gz: '08824b7349988634bc2d0c4ab3e3ad7757d4491e1617c68b44359e42aafacd51'
5
5
  SHA512:
6
- metadata.gz: 0a04211a36264ac886ecb8b7260c23e767de8364725fdc39dbfbe508116a5201ec8a54f4edf1865b3b91c8ee0fca4d2152d7f1091fca056501995723e4edbce6
7
- data.tar.gz: d0ac8d6a477e357ff6582079443b6221100d868e1234d7aaaa4a815108b0db2e024e84086e2ace0c3dc3d1b85b8cd40701737f4bc292dabf479ca6263bccd994
6
+ metadata.gz: 31d6affbeb9376696c84d6424a55df7dbbab839f37f245da53185ad488a21bbad1871c1ac0f42cddd912388a172af4b7341e3a92287b47622a957b80c9d59061
7
+ data.tar.gz: 6ead47a3741c1a6d859771b532391dd8aa29f64554884eb042aca67964643c0cfc009662ad5d2939f090ce1b83fa1dff12d004c5ecaee22d5bf538e783b722e3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [9.5.0](https://github.com/ClosureTree/closure_tree/compare/closure_tree-v9.3.0...closure_tree/v9.5.0) (2026-01-21)
4
+
5
+
6
+ ### Features
7
+
8
+ * Add `dependent: :adopt` option for `has_closure_tree` ([#471](https://github.com/ClosureTree/closure_tree/issues/471)) ([d47d7c9](https://github.com/ClosureTree/closure_tree/commit/d47d7c93b59327112a68a7f8454a278877b6ba77))
9
+ * add PostgreSQL schema-qualified table name support ([#462](https://github.com/ClosureTree/closure_tree/issues/462)) ([5f9006c](https://github.com/ClosureTree/closure_tree/commit/5f9006cece95a76f665cb50c2615317e3fa48586))
10
+ * Add runtime advisory lock name customization and multi-database documentation ([#454](https://github.com/ClosureTree/closure_tree/issues/454)) ([d6ffd73](https://github.com/ClosureTree/closure_tree/commit/d6ffd7381e25a28f7a4742bfa2d9c893f0115395))
11
+ * migrate from ActiveSupport::Autoload to Zeitwerk ([#457](https://github.com/ClosureTree/closure_tree/issues/457)) ([d18e80c](https://github.com/ClosureTree/closure_tree/commit/d18e80cdbd4f3510377363bc7b5166f0cc1b0a6f))
12
+ * rewrite with clean api ([#451](https://github.com/ClosureTree/closure_tree/issues/451)) ([f56f2e1](https://github.com/ClosureTree/closure_tree/commit/f56f2e1a3490bb8a099cea8f80b676945fce1c2e))
13
+ * use with_advisory_lock that support rails 8.2 ([#479](https://github.com/ClosureTree/closure_tree/issues/479)) ([bca6231](https://github.com/ClosureTree/closure_tree/commit/bca623168d3255f57e186a3bdcd39e00d38e2a7c))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * add implicit_order_column for Rails 8.1+ compatibility ([#464](https://github.com/ClosureTree/closure_tree/issues/464)) ([f384303](https://github.com/ClosureTree/closure_tree/commit/f38430334a79ea15d236f9212118dcb5e4530746))
19
+ * configure release-please to recognize v8.0.0 release ([#455](https://github.com/ClosureTree/closure_tree/issues/455)) ([fc34f21](https://github.com/ClosureTree/closure_tree/commit/fc34f2148570afd83608b07a3f5282e5fd475783))
20
+ * eager loading of hierarchies by defining the primary key ([#465](https://github.com/ClosureTree/closure_tree/issues/465)) ([8c7e490](https://github.com/ClosureTree/closure_tree/commit/8c7e490ff89239e44aee71577288bff0e177a8d6))
21
+ * hierarchy class inheritance to avoid STI validations ([#392](https://github.com/ClosureTree/closure_tree/issues/392)) ([#472](https://github.com/ClosureTree/closure_tree/issues/472)) ([73b07a6](https://github.com/ClosureTree/closure_tree/commit/73b07a6cfeec226af16f51ce7c9c6c1d6d921b3d))
22
+ * improve api usage ([#475](https://github.com/ClosureTree/closure_tree/issues/475)) ([cde4d29](https://github.com/ClosureTree/closure_tree/commit/cde4d292236b4267ac4c0be2e2b21092a8b6298c))
23
+ * nil scope values being excluded from scope filtering ([#476](https://github.com/ClosureTree/closure_tree/issues/476)) ([f45880e](https://github.com/ClosureTree/closure_tree/commit/f45880e9765bfa3c1f171b7386bf5a5584208ad7))
24
+ * restore proper deprecation for database_less configuration ([#459](https://github.com/ClosureTree/closure_tree/issues/459)) ([de8b402](https://github.com/ClosureTree/closure_tree/commit/de8b40233d3de5243afce7ec9de9ad26c2eee181))
25
+
3
26
  ## [9.3.0](https://github.com/ClosureTree/closure_tree/compare/closure_tree/v9.2.0...closure_tree/v9.3.0) (2025-11-19)
4
27
 
5
28
 
data/README.md CHANGED
@@ -314,6 +314,7 @@ When you include ```has_closure_tree``` in your model, you can provide a hash to
314
314
  * ```:hierarchy_table_name``` to override the hierarchy table name. This defaults to the singular name of the model + "_hierarchies", like ```tag_hierarchies```.
315
315
  * ```:dependent``` determines what happens when a node is destroyed. Defaults to ```nullify```.
316
316
  * ```:nullify``` will simply set the parent column to null. Each child node will be considered a "root" node. This is the default.
317
+ * ```:adopt``` will move children to their grandparent (parent's parent). If there is no grandparent, children become root nodes. This is useful for maintaining tree structure when removing intermediate nodes.
317
318
  * ```:delete_all``` will delete all descendant nodes (which circumvents the destroy hooks)
318
319
  * ```:destroy``` will destroy all descendant nodes (which runs the destroy hooks on each child node)
319
320
  * ```nil``` does nothing with descendant nodes
data/closure_tree.gemspec CHANGED
@@ -26,11 +26,11 @@ Gem::Specification.new do |gem|
26
26
  gem.required_ruby_version = '>= 3.3.0'
27
27
 
28
28
  gem.add_dependency 'activerecord', '>= 7.2.0'
29
- gem.add_dependency 'with_advisory_lock', '>= 7.0.0'
29
+ gem.add_dependency 'with_advisory_lock', '>= 7.5.0'
30
30
  gem.add_dependency 'zeitwerk', '~> 2.7'
31
31
 
32
32
  gem.add_development_dependency 'database_cleaner'
33
- gem.add_development_dependency 'minitest'
33
+ gem.add_development_dependency 'minitest', '~> 5.0'
34
34
  gem.add_development_dependency 'minitest-reporters'
35
35
  gem.add_development_dependency 'parallel'
36
36
  gem.add_development_dependency 'simplecov'
@@ -79,5 +79,13 @@ module ClosureTree
79
79
 
80
80
  delete_manager
81
81
  end
82
+
83
+ # Convert an Arel AST to SQL using the correct connection's visitor
84
+ # This ensures proper quoting for the specific database adapter (MySQL uses backticks, PostgreSQL uses double quotes)
85
+ def to_sql_with_connection(arel_manager)
86
+ collector = Arel::Collectors::SQLString.new
87
+ visitor = connection.visitor
88
+ visitor.accept(arel_manager.ast, collector).value
89
+ end
82
90
  end
83
91
  end
@@ -20,7 +20,7 @@ module ClosureTree
20
20
 
21
21
  has_many :children, *_ct.has_many_order_with_option, class_name: _ct.model_class.to_s,
22
22
  foreign_key: _ct.parent_column_name,
23
- dependent: _ct.options[:dependent],
23
+ dependent: _ct.options[:dependent] == :adopt ? :nullify : _ct.options[:dependent],
24
24
  inverse_of: :parent do
25
25
  # We have to redefine hash_tree because the activerecord relation is already scoped to parent_id.
26
26
  def hash_tree(options = {})
@@ -47,4 +47,4 @@ module ClosureTree
47
47
  source: :descendant
48
48
  end
49
49
  end
50
- end
50
+ end
@@ -52,12 +52,26 @@ module ClosureTree
52
52
 
53
53
  def _ct_before_destroy
54
54
  _ct.with_advisory_lock do
55
+ _ct_adopt_children_to_grandparent if _ct.options[:dependent] == :adopt
55
56
  delete_hierarchy_references
56
57
  self.class.find(id).children.find_each(&:rebuild!) if _ct.options[:dependent] == :nullify
57
58
  end
58
59
  true # don't prevent destruction
59
60
  end
60
61
 
62
+ private def _ct_adopt_children_to_grandparent
63
+ grandparent_id = read_attribute(_ct.parent_column_name)
64
+ children_ids = self.class.where(_ct.parent_column_name => id).pluck(:id)
65
+
66
+ return if children_ids.empty?
67
+
68
+ # Update all children's parent_id in a single query
69
+ self.class.where(id: children_ids).update_all(_ct.parent_column_name => grandparent_id)
70
+
71
+ # Rebuild hierarchy for each child
72
+ self.class.where(id: children_ids).find_each(&:rebuild!)
73
+ end
74
+
61
75
  def rebuild!(called_by_rebuild = false)
62
76
  _ct.with_advisory_lock do
63
77
  delete_hierarchy_references unless (defined? @was_new_record) && @was_new_record
@@ -93,7 +107,7 @@ module ClosureTree
93
107
 
94
108
  hierarchy_table = hierarchy_class.arel_table
95
109
  delete_query = _ct.build_hierarchy_delete_query(hierarchy_table, id)
96
- _ct.connection.execute(delete_query.to_sql)
110
+ _ct.connection.execute(_ct.to_sql_with_connection(delete_query))
97
111
  end
98
112
  end
99
113
 
@@ -16,7 +16,7 @@ module ClosureTree
16
16
 
17
17
  @options = {
18
18
  parent_column_name: 'parent_id',
19
- dependent: :nullify, # or :destroy or :delete_all -- see the README
19
+ dependent: :nullify, # or :destroy, :delete_all, or :adopt -- see the README
20
20
  name_column: 'name',
21
21
  with_advisory_lock: true, # This will be overridden by adapter support
22
22
  numeric_order: false
@@ -35,9 +35,25 @@ module ClosureTree
35
35
  extend NumericOrderSupport.adapter_for_connection(connection)
36
36
  end
37
37
 
38
+ # Find the abstract base class for database connection
39
+ # This ensures hierarchy class uses the same database but doesn't inherit
40
+ # validations/callbacks from STI parent classes (issue #392)
41
+ def abstract_base_class
42
+ klass = model_class
43
+ while klass.superclass != ActiveRecord::Base
44
+ parent = klass.superclass
45
+ # Stop at abstract class (ApplicationRecord, SecondaryRecord, etc.)
46
+ return parent if parent.abstract_class?
47
+ # Stop at connection boundary (handles non-abstract parents with custom connections)
48
+ return parent if parent.connection_specification_name != parent.superclass.connection_specification_name
49
+ klass = parent
50
+ end
51
+ ActiveRecord::Base
52
+ end
53
+
38
54
  def hierarchy_class_for_model
39
55
  parent_class = model_class.module_parent
40
- hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(model_class.superclass))
56
+ hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(abstract_base_class))
41
57
  model_class_name = model_class.to_s
42
58
  hierarchy_class.class_eval do
43
59
  # Rails 8.1+ requires an implicit_order_column for models without a primary key
@@ -222,12 +238,12 @@ module ClosureTree
222
238
  case scope_option
223
239
  when Symbol
224
240
  value = instance.read_attribute(scope_option)
225
- scope_hash[scope_option] = value unless value.nil?
241
+ scope_hash[scope_option] = value
226
242
  when Array
227
243
  scope_option.each do |item|
228
244
  if item.is_a?(Symbol)
229
245
  value = instance.read_attribute(item)
230
- scope_hash[item] = value unless value.nil?
246
+ scope_hash[item] = value
231
247
  end
232
248
  end
233
249
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClosureTree
4
- VERSION = Gem::Version.new('9.3.0')
4
+ VERSION = Gem::Version.new('9.5.0')
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: closure_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.3.0
4
+ version: 9.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 7.0.0
33
+ version: 7.5.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 7.0.0
40
+ version: 7.5.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: zeitwerk
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -70,16 +70,16 @@ dependencies:
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '5.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '5.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: minitest-reporters
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -178,7 +178,7 @@ licenses:
178
178
  metadata:
179
179
  bug_tracker_uri: https://github.com/ClosureTree/closure_tree/issues
180
180
  changelog_uri: https://github.com/ClosureTree/closure_tree/blob/master/CHANGELOG.md
181
- documentation_uri: https://www.rubydoc.info/gems/closure_tree/9.3.0
181
+ documentation_uri: https://www.rubydoc.info/gems/closure_tree/9.5.0
182
182
  homepage_uri: https://closuretree.github.io/closure_tree/
183
183
  source_code_uri: https://github.com/ClosureTree/closure_tree
184
184
  rubygems_mfa_required: 'true'