Skip to content

Commit 73b07a6

Browse files
authored
fix: hierarchy class inheritance to avoid STI validations (#392) (#472)
1 parent c5c8b04 commit 73b07a6

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

lib/closure_tree/support.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,25 @@ def initialize(model_class, options)
3535
extend NumericOrderSupport.adapter_for_connection(connection)
3636
end
3737

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, MysqlRecord, 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+
3854
def hierarchy_class_for_model
3955
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))
4157
model_class_name = model_class.to_s
4258
hierarchy_class.class_eval do
4359
# Rails 8.1+ requires an implicit_order_column for models without a primary key
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
5+
class HierarchyInheritanceTest < ActiveSupport::TestCase
6+
# Test for issue #392: Hierarchy class should inherit from same abstract base as model
7+
# but NOT from STI parent (to avoid inheriting validations/callbacks)
8+
test 'MetalHierarchy inherits from same connection class as Metal' do
9+
# Force MetalHierarchy to be loaded
10+
Metal._ct
11+
12+
# Metal < ApplicationRecord (abstract connection class)
13+
# Adamantium < Metal (STI child)
14+
# MetalHierarchy should inherit from ApplicationRecord, NOT Metal
15+
assert_equal Metal.superclass, MetalHierarchy.superclass,
16+
"MetalHierarchy should inherit from same abstract base as Metal (#{Metal.superclass})"
17+
18+
# Verify it's the abstract class, not the STI parent
19+
assert MetalHierarchy.superclass.abstract_class?,
20+
"MetalHierarchy should inherit from abstract class"
21+
22+
# The hierarchy class should NOT inherit validations from Metal
23+
assert_not_equal Metal.validators.size, MetalHierarchy.validators.size,
24+
"MetalHierarchy should not inherit validations from Metal"
25+
end
26+
27+
test 'Adamantium inherits has_closure_tree and uses same hierarchy as Metal' do
28+
# Adamantium < Metal (STI - should inherit has_closure_tree)
29+
30+
# Verify Adamantium inherited has_closure_tree
31+
assert_respond_to Adamantium, :_ct, "Adamantium should inherit has_closure_tree from Metal"
32+
33+
# Both should use the same hierarchy class (MetalHierarchy)
34+
assert_equal Metal.hierarchy_class, Adamantium.hierarchy_class,
35+
"Adamantium should use same hierarchy class as Metal (STI)"
36+
37+
# The hierarchy class should inherit from ApplicationRecord, NOT Metal
38+
assert_equal ApplicationRecord, MetalHierarchy.superclass,
39+
"MetalHierarchy should inherit from ApplicationRecord, not Metal"
40+
end
41+
end

0 commit comments

Comments
 (0)