Skip to content

fix: restore proper deprecation for database_less configuration #459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 15 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Note that closure_tree only supports ActiveRecord 7.2 and later, and has test co

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

**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.

**IMPORTANT: Make sure you add `has_closure_tree` _after_ `attr_accessible` and
`self.table_name =` lines in your model.**

Expand Down Expand Up @@ -156,10 +158,10 @@ Then:

```ruby
grandparent.self_and_descendants.collect(&:name)
=> ["Grandparent", "Parent", "First Child", "Second Child", "Third Child", "Fourth Child"]
#=> ["Grandparent", "Parent", "First Child", "Second Child", "Third Child", "Fourth Child"]

child1.ancestry_path
=> ["Grandparent", "Parent", "First Child"]
#=> ["Grandparent", "Parent", "First Child"]
```

### find_or_create_by_path
Expand Down Expand Up @@ -204,19 +206,16 @@ d = Tag.find_or_create_by_path %w[a b c d]
h = Tag.find_or_create_by_path %w[e f g h]
e = h.root
d.add_child(e) # "d.children << e" would work too, of course
h.ancestry_path
=> ["a", "b", "c", "d", "e", "f", "g", "h"]
h.ancestry_path #=> ["a", "b", "c", "d", "e", "f", "g", "h"]
```

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:

```ruby
j = Tag.find 102
j.self_and_ancestor_ids
=> [102, 87, 77]
j.self_and_ancestor_ids #=> [102, 87, 77]
j.update parent_id: 96
j.self_and_ancestor_ids
=> [102, 96, 95, 78]
j.self_and_ancestor_ids #=> [102, 96, 95, 78]
```

### Nested hashes
Expand All @@ -233,17 +232,13 @@ c1 = d1.parent
d2 = b.find_or_create_by_path %w(c2 d2)
c2 = d2.parent

Tag.hash_tree
=> {a => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}, b2 => {}}}
Tag.hash_tree #=> {a => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}, b2 => {}}}

Tag.hash_tree(:limit_depth => 2)
=> {a => {b => {}, b2 => {}}}
Tag.hash_tree(:limit_depth => 2) #=> {a => {b => {}, b2 => {}}}

b.hash_tree
=> {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}}
b.hash_tree #=> {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}}

b.hash_tree(:limit_depth => 2)
=> {b => {c1 => {}, c2 => {}}}
b.hash_tree(:limit_depth => 2) #=> {b => {c1 => {}, c2 => {}}}
```

**If your tree is large (or might become so), use :limit_depth.**
Expand Down Expand Up @@ -477,20 +472,16 @@ c = OrderedTag.create(name: 'c')
# We have to call 'root.reload.children' because root won't be in sync with the database otherwise:

a.append_sibling(b)
root.reload.children.pluck(:name)
=> ["a", "b"]
root.reload.children.pluck(:name) #=> ["a", "b"]

a.prepend_sibling(b)
root.reload.children.pluck(:name)
=> ["b", "a"]
root.reload.children.pluck(:name) #=> ["b", "a"]

a.append_sibling(c)
root.reload.children.pluck(:name)
=> ["b", "a", "c"]
root.reload.children.pluck(:name) #=> ["b", "a", "c"]

b.append_sibling(c)
root.reload.children.pluck(:name)
=> ["b", "c", "a"]
root.reload.children.pluck(:name) #=> ["b", "c", "a"]
```

### Ordering Roots
Expand Down
15 changes: 10 additions & 5 deletions lib/closure_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@

module ClosureTree
def self.configure
ActiveSupport::Deprecation.new.warn(
'ClosureTree.configure is deprecated and will be removed in a future version. ' \
'Configuration is no longer needed.'
)
yield if block_given?
if block_given?
# Create a temporary configuration object to capture deprecated settings
config = Configuration.new
yield config
else
ActiveSupport::Deprecation.new.warn(
'ClosureTree.configure is deprecated and will be removed in a future version. ' \
'Configuration is no longer needed.'
)
end
end
end

Expand Down
50 changes: 50 additions & 0 deletions lib/closure_tree/association_setup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

require 'active_support/concern'

module ClosureTree
# This concern sets up the ActiveRecord associations after all other modules are included.
# It must be included last to ensure that HierarchyMaintenance callbacks are already set up.
module AssociationSetup
extend ActiveSupport::Concern

included do
belongs_to :parent, nil,
class_name: _ct.model_class.to_s,
foreign_key: _ct.parent_column_name,
inverse_of: :children,
touch: _ct.options[:touch],
optional: true

order_by_generations = -> { Arel.sql("#{_ct.quoted_hierarchy_table_name}.generations ASC") }

has_many :children, *_ct.has_many_order_with_option, class_name: _ct.model_class.to_s,
foreign_key: _ct.parent_column_name,
dependent: _ct.options[:dependent],
inverse_of: :parent do
# We have to redefine hash_tree because the activerecord relation is already scoped to parent_id.
def hash_tree(options = {})
# we want limit_depth + 1 because we don't do self_and_descendants.
limit_depth = options[:limit_depth]
_ct.hash_tree(@association.owner.descendants, limit_depth ? limit_depth + 1 : nil)
end
end

has_many :ancestor_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
class_name: _ct.hierarchy_class_name,
foreign_key: 'descendant_id'

has_many :self_and_ancestors, *_ct.has_many_order_without_option(order_by_generations),
through: :ancestor_hierarchies,
source: :ancestor

has_many :descendant_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
class_name: _ct.hierarchy_class_name,
foreign_key: 'ancestor_id'

has_many :self_and_descendants, *_ct.has_many_order_with_option(order_by_generations),
through: :descendant_hierarchies,
source: :descendant
end
end
end
22 changes: 22 additions & 0 deletions lib/closure_tree/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module ClosureTree
# Minimal configuration class to handle deprecated options
class Configuration
def database_less=(_value)
ActiveSupport::Deprecation.new.warn(
'ClosureTree.configure { |config| config.database_less = true } is deprecated ' \
'and will be removed in v10.0.0. The database_less option is no longer needed ' \
'for modern deployment practices. Remove this configuration from your initializer.'
)
# Ignore the value - this is a no-op for backward compatibility
end

def database_less?
false # Always return false since this option does nothing
end

# Keep the old method name for backward compatibility
alias database_less database_less?
end
end
9 changes: 7 additions & 2 deletions lib/closure_tree/has_closure_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ def has_closure_tree(options = {})
class_attribute :hierarchy_class
self.hierarchy_class = _ct.hierarchy_class_for_model

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

# Include AssociationSetup last to ensure all dependencies are ready
include ClosureTree::AssociationSetup

connection_pool.release_connection
end

alias acts_as_tree has_closure_tree
# Only alias acts_as_tree if it's not already defined (to avoid conflicts with other gems)
alias acts_as_tree has_closure_tree unless method_defined?(:acts_as_tree)
end
end
39 changes: 0 additions & 39 deletions lib/closure_tree/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,6 @@ module ClosureTree
module Model
extend ActiveSupport::Concern

included do
belongs_to :parent, nil,
class_name: _ct.model_class.to_s,
foreign_key: _ct.parent_column_name,
inverse_of: :children,
touch: _ct.options[:touch],
optional: true

order_by_generations = -> { Arel.sql("#{_ct.quoted_hierarchy_table_name}.generations ASC") }

has_many :children, *_ct.has_many_order_with_option, class_name: _ct.model_class.to_s,
foreign_key: _ct.parent_column_name,
dependent: _ct.options[:dependent],
inverse_of: :parent do
# We have to redefine hash_tree because the activerecord relation is already scoped to parent_id.
def hash_tree(options = {})
# we want limit_depth + 1 because we don't do self_and_descendants.
limit_depth = options[:limit_depth]
_ct.hash_tree(@association.owner.descendants, limit_depth ? limit_depth + 1 : nil)
end
end

has_many :ancestor_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
class_name: _ct.hierarchy_class_name,
foreign_key: 'descendant_id'

has_many :self_and_ancestors, *_ct.has_many_order_without_option(order_by_generations),
through: :ancestor_hierarchies,
source: :ancestor

has_many :descendant_hierarchies, *_ct.has_many_order_without_option(order_by_generations),
class_name: _ct.hierarchy_class_name,
foreign_key: 'ancestor_id'

has_many :self_and_descendants, *_ct.has_many_order_with_option(order_by_generations),
through: :descendant_hierarchies,
source: :descendant
end

# Delegate to the Support instance on the class:
def _ct
self.class._ct
Expand Down