Skip to content

Commit de8b402

Browse files
authored
fix: restore proper deprecation for database_less configuration (#459)
The database_less configuration option was removed in v9.1.0 without proper deprecation, causing deployments to fail when upgrading if they had this configuration in their production.rb or initializers. This change restores backward compatibility by: - Adding a minimal Configuration class that captures the deprecated database_less option - Showing specific deprecation warnings when database_less is used - Making the option a no-op (always returns false) since it's no longer needed - Providing clear migration guidance for users to remove their configuration The deprecation timeline indicates removal in v10.0.0, giving users time to migrate away from this obsolete configuration without breaking deployments. Also includes improvements to HasClosureTree include order dependency and README formatting fixes to use inline return value comments. Fixes the missing deprecation that was introduced when migrating to Zeitwerk.
1 parent 31b738f commit de8b402

File tree

6 files changed

+104
-70
lines changed

6 files changed

+104
-70
lines changed

README.md

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ Note that closure_tree only supports ActiveRecord 7.2 and later, and has test co
7373

7474
Make sure you check out the [large number of options](#available-options) that `has_closure_tree` accepts.
7575

76+
**Note:** The `acts_as_tree` alias is only created if your model doesn't already have a method with that name. This prevents conflicts with other gems like the original `acts_as_tree` gem.
77+
7678
**IMPORTANT: Make sure you add `has_closure_tree` _after_ `attr_accessible` and
7779
`self.table_name =` lines in your model.**
7880
@@ -156,10 +158,10 @@ Then:
156158
157159
```ruby
158160
grandparent.self_and_descendants.collect(&:name)
159-
=> ["Grandparent", "Parent", "First Child", "Second Child", "Third Child", "Fourth Child"]
161+
#=> ["Grandparent", "Parent", "First Child", "Second Child", "Third Child", "Fourth Child"]
160162
161163
child1.ancestry_path
162-
=> ["Grandparent", "Parent", "First Child"]
164+
#=> ["Grandparent", "Parent", "First Child"]
163165
```
164166
165167
### find_or_create_by_path
@@ -204,19 +206,16 @@ d = Tag.find_or_create_by_path %w[a b c d]
204206
h = Tag.find_or_create_by_path %w[e f g h]
205207
e = h.root
206208
d.add_child(e) # "d.children << e" would work too, of course
207-
h.ancestry_path
208-
=> ["a", "b", "c", "d", "e", "f", "g", "h"]
209+
h.ancestry_path #=> ["a", "b", "c", "d", "e", "f", "g", "h"]
209210
```
210211
211212
When it is more convenient to simply change the `parent_id` of a node directly (for example, when dealing with a form `<select>`), closure_tree will handle the necessary changes automatically when the record is saved:
212213
213214
```ruby
214215
j = Tag.find 102
215-
j.self_and_ancestor_ids
216-
=> [102, 87, 77]
216+
j.self_and_ancestor_ids #=> [102, 87, 77]
217217
j.update parent_id: 96
218-
j.self_and_ancestor_ids
219-
=> [102, 96, 95, 78]
218+
j.self_and_ancestor_ids #=> [102, 96, 95, 78]
220219
```
221220
222221
### Nested hashes
@@ -233,17 +232,13 @@ c1 = d1.parent
233232
d2 = b.find_or_create_by_path %w(c2 d2)
234233
c2 = d2.parent
235234
236-
Tag.hash_tree
237-
=> {a => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}, b2 => {}}}
235+
Tag.hash_tree #=> {a => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}, b2 => {}}}
238236
239-
Tag.hash_tree(:limit_depth => 2)
240-
=> {a => {b => {}, b2 => {}}}
237+
Tag.hash_tree(:limit_depth => 2) #=> {a => {b => {}, b2 => {}}}
241238
242-
b.hash_tree
243-
=> {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}}
239+
b.hash_tree #=> {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}}
244240
245-
b.hash_tree(:limit_depth => 2)
246-
=> {b => {c1 => {}, c2 => {}}}
241+
b.hash_tree(:limit_depth => 2) #=> {b => {c1 => {}, c2 => {}}}
247242
```
248243
249244
**If your tree is large (or might become so), use :limit_depth.**
@@ -477,20 +472,16 @@ c = OrderedTag.create(name: 'c')
477472
# We have to call 'root.reload.children' because root won't be in sync with the database otherwise:
478473
479474
a.append_sibling(b)
480-
root.reload.children.pluck(:name)
481-
=> ["a", "b"]
475+
root.reload.children.pluck(:name) #=> ["a", "b"]
482476
483477
a.prepend_sibling(b)
484-
root.reload.children.pluck(:name)
485-
=> ["b", "a"]
478+
root.reload.children.pluck(:name) #=> ["b", "a"]
486479
487480
a.append_sibling(c)
488-
root.reload.children.pluck(:name)
489-
=> ["b", "a", "c"]
481+
root.reload.children.pluck(:name) #=> ["b", "a", "c"]
490482
491483
b.append_sibling(c)
492-
root.reload.children.pluck(:name)
493-
=> ["b", "c", "a"]
484+
root.reload.children.pluck(:name) #=> ["b", "c", "a"]
494485
```
495486

496487
### Ordering Roots

lib/closure_tree.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99

1010
module ClosureTree
1111
def self.configure
12-
ActiveSupport::Deprecation.new.warn(
13-
'ClosureTree.configure is deprecated and will be removed in a future version. ' \
14-
'Configuration is no longer needed.'
15-
)
16-
yield if block_given?
12+
if block_given?
13+
# Create a temporary configuration object to capture deprecated settings
14+
config = Configuration.new
15+
yield config
16+
else
17+
ActiveSupport::Deprecation.new.warn(
18+
'ClosureTree.configure is deprecated and will be removed in a future version. ' \
19+
'Configuration is no longer needed.'
20+
)
21+
end
1722
end
1823
end
1924

lib/closure_tree/association_setup.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# frozen_string_literal: true
2+
3+
require 'active_support/concern'
4+
5+
module ClosureTree
6+
# This concern sets up the ActiveRecord associations after all other modules are included.
7+
# It must be included last to ensure that HierarchyMaintenance callbacks are already set up.
8+
module AssociationSetup
9+
extend ActiveSupport::Concern
10+
11+
included do
12+
belongs_to :parent, nil,
13+
class_name: _ct.model_class.to_s,
14+
foreign_key: _ct.parent_column_name,
15+
inverse_of: :children,
16+
touch: _ct.options[:touch],
17+
optional: true
18+
19+
order_by_generations = -> { Arel.sql("#{_ct.quoted_hierarchy_table_name}.generations ASC") }
20+
21+
has_many :children, *_ct.has_many_order_with_option, class_name: _ct.model_class.to_s,
22+
foreign_key: _ct.parent_column_name,
23+
dependent: _ct.options[:dependent],
24+
inverse_of: :parent do
25+
# We have to redefine hash_tree because the activerecord relation is already scoped to parent_id.
26+
def hash_tree(options = {})
27+
# we want limit_depth + 1 because we don't do self_and_descendants.
28+
limit_depth = options[:limit_depth]
29+
_ct.hash_tree(@association.owner.descendants, limit_depth ? limit_depth + 1 : nil)
30+
end
31+
end
32+
33+
has_many :ancestor_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
34+
class_name: _ct.hierarchy_class_name,
35+
foreign_key: 'descendant_id'
36+
37+
has_many :self_and_ancestors, *_ct.has_many_order_without_option(order_by_generations),
38+
through: :ancestor_hierarchies,
39+
source: :ancestor
40+
41+
has_many :descendant_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
42+
class_name: _ct.hierarchy_class_name,
43+
foreign_key: 'ancestor_id'
44+
45+
has_many :self_and_descendants, *_ct.has_many_order_with_option(order_by_generations),
46+
through: :descendant_hierarchies,
47+
source: :descendant
48+
end
49+
end
50+
end

lib/closure_tree/configuration.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
module ClosureTree
4+
# Minimal configuration class to handle deprecated options
5+
class Configuration
6+
def database_less=(_value)
7+
ActiveSupport::Deprecation.new.warn(
8+
'ClosureTree.configure { |config| config.database_less = true } is deprecated ' \
9+
'and will be removed in v10.0.0. The database_less option is no longer needed ' \
10+
'for modern deployment practices. Remove this configuration from your initializer.'
11+
)
12+
# Ignore the value - this is a no-op for backward compatibility
13+
end
14+
15+
def database_less?
16+
false # Always return false since this option does nothing
17+
end
18+
19+
# Keep the old method name for backward compatibility
20+
alias database_less database_less?
21+
end
22+
end

lib/closure_tree/has_closure_tree.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ def has_closure_tree(options = {})
2525
class_attribute :hierarchy_class
2626
self.hierarchy_class = _ct.hierarchy_class_for_model
2727

28-
# tests fail if you include Model before HierarchyMaintenance wtf
28+
# Include modules - HierarchyMaintenance provides callbacks that Model associations depend on
29+
# The order is maintained for consistency, but associations are now set up after all includes
2930
include ClosureTree::HierarchyMaintenance
3031
include ClosureTree::Model
3132
include ClosureTree::Finders
@@ -35,9 +36,13 @@ def has_closure_tree(options = {})
3536
include ClosureTree::DeterministicOrdering if _ct.order_option?
3637
include ClosureTree::NumericDeterministicOrdering if _ct.order_is_numeric?
3738

39+
# Include AssociationSetup last to ensure all dependencies are ready
40+
include ClosureTree::AssociationSetup
41+
3842
connection_pool.release_connection
3943
end
4044

41-
alias acts_as_tree has_closure_tree
45+
# Only alias acts_as_tree if it's not already defined (to avoid conflicts with other gems)
46+
alias acts_as_tree has_closure_tree unless method_defined?(:acts_as_tree)
4247
end
4348
end

lib/closure_tree/model.rb

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,6 @@ module ClosureTree
66
module Model
77
extend ActiveSupport::Concern
88

9-
included do
10-
belongs_to :parent, nil,
11-
class_name: _ct.model_class.to_s,
12-
foreign_key: _ct.parent_column_name,
13-
inverse_of: :children,
14-
touch: _ct.options[:touch],
15-
optional: true
16-
17-
order_by_generations = -> { Arel.sql("#{_ct.quoted_hierarchy_table_name}.generations ASC") }
18-
19-
has_many :children, *_ct.has_many_order_with_option, class_name: _ct.model_class.to_s,
20-
foreign_key: _ct.parent_column_name,
21-
dependent: _ct.options[:dependent],
22-
inverse_of: :parent do
23-
# We have to redefine hash_tree because the activerecord relation is already scoped to parent_id.
24-
def hash_tree(options = {})
25-
# we want limit_depth + 1 because we don't do self_and_descendants.
26-
limit_depth = options[:limit_depth]
27-
_ct.hash_tree(@association.owner.descendants, limit_depth ? limit_depth + 1 : nil)
28-
end
29-
end
30-
31-
has_many :ancestor_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
32-
class_name: _ct.hierarchy_class_name,
33-
foreign_key: 'descendant_id'
34-
35-
has_many :self_and_ancestors, *_ct.has_many_order_without_option(order_by_generations),
36-
through: :ancestor_hierarchies,
37-
source: :ancestor
38-
39-
has_many :descendant_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
40-
class_name: _ct.hierarchy_class_name,
41-
foreign_key: 'ancestor_id'
42-
43-
has_many :self_and_descendants, *_ct.has_many_order_with_option(order_by_generations),
44-
through: :descendant_hierarchies,
45-
source: :descendant
46-
end
47-
489
# Delegate to the Support instance on the class:
4910
def _ct
5011
self.class._ct

0 commit comments

Comments
 (0)