Skip to content
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
18 changes: 17 additions & 1 deletion lib/closure_tree/support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,25 @@ def initialize(model_class, options)
extend NumericOrderSupport.adapter_for_connection(connection)
end

# Find the abstract base class for database connection
# This ensures hierarchy class uses the same database but doesn't inherit
# validations/callbacks from STI parent classes (issue #392)
def abstract_base_class
klass = model_class
while klass.superclass != ActiveRecord::Base
parent = klass.superclass
# Stop at abstract class (ApplicationRecord, MysqlRecord, etc.)
return parent if parent.abstract_class?
# Stop at connection boundary (handles non-abstract parents with custom connections)
return parent if parent.connection_specification_name != parent.superclass.connection_specification_name
klass = parent
end
ActiveRecord::Base
end

def hierarchy_class_for_model
parent_class = model_class.module_parent
hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(model_class.superclass))
hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(abstract_base_class))
model_class_name = model_class.to_s
hierarchy_class.class_eval do
# Rails 8.1+ requires an implicit_order_column for models without a primary key
Expand Down
41 changes: 41 additions & 0 deletions test/closure_tree/hierarchy_inheritance_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require 'test_helper'

class HierarchyInheritanceTest < ActiveSupport::TestCase
# Test for issue #392: Hierarchy class should inherit from same abstract base as model
# but NOT from STI parent (to avoid inheriting validations/callbacks)
test 'MetalHierarchy inherits from same connection class as Metal' do
# Force MetalHierarchy to be loaded
Metal._ct

# Metal < ApplicationRecord (abstract connection class)
# Adamantium < Metal (STI child)
# MetalHierarchy should inherit from ApplicationRecord, NOT Metal
assert_equal Metal.superclass, MetalHierarchy.superclass,
"MetalHierarchy should inherit from same abstract base as Metal (#{Metal.superclass})"

# Verify it's the abstract class, not the STI parent
assert MetalHierarchy.superclass.abstract_class?,
"MetalHierarchy should inherit from abstract class"

# The hierarchy class should NOT inherit validations from Metal
assert_not_equal Metal.validators.size, MetalHierarchy.validators.size,
"MetalHierarchy should not inherit validations from Metal"
end

test 'Adamantium inherits has_closure_tree and uses same hierarchy as Metal' do
# Adamantium < Metal (STI - should inherit has_closure_tree)

# Verify Adamantium inherited has_closure_tree
assert_respond_to Adamantium, :_ct, "Adamantium should inherit has_closure_tree from Metal"

# Both should use the same hierarchy class (MetalHierarchy)
assert_equal Metal.hierarchy_class, Adamantium.hierarchy_class,
"Adamantium should use same hierarchy class as Metal (STI)"

# The hierarchy class should inherit from ApplicationRecord, NOT Metal
assert_equal ApplicationRecord, MetalHierarchy.superclass,
"MetalHierarchy should inherit from ApplicationRecord, not Metal"
end
end