Skip to content

Commit 5f9006c

Browse files
authored
feat: add PostgreSQL schema-qualified table name support (#462)
Add support for PostgreSQL schema-qualified table names (e.g., 'test_schema.schema_types') to properly generate hierarchy table names with schema prefix.
1 parent 0f3b0f6 commit 5f9006c

File tree

5 files changed

+105
-3
lines changed

5 files changed

+105
-3
lines changed

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ruby 3.4.4
1+
ruby 3.4.7

lib/closure_tree/support.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,19 @@ def hierarchy_table_name
5151
# We need to use the table_name, not something like ct_class.to_s.demodulize + "_hierarchies",
5252
# because they may have overridden the table name, which is what we want to be consistent with
5353
# in order for the schema to make sense.
54-
tablename = options[:hierarchy_table_name] ||
55-
"#{remove_prefix_and_suffix(table_name, model_class).singularize}_hierarchies"
54+
if options[:hierarchy_table_name]
55+
tablename = options[:hierarchy_table_name]
56+
else
57+
base_table = remove_prefix_and_suffix(table_name, model_class)
58+
59+
# Handle PostgreSQL schema-qualified table names (e.g., "my_schema.table_name")
60+
schema, _, table = base_table.rpartition('.')
61+
if schema.present?
62+
tablename = "#{schema}.#{table.singularize}_hierarchies"
63+
else
64+
tablename = "#{table.singularize}_hierarchies"
65+
end
66+
end
5667

5768
[model_class.table_name_prefix, tablename, model_class.table_name_suffix].join
5869
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
5+
describe SchemaType do
6+
before do
7+
skip 'PostgreSQL only' unless postgresql?
8+
end
9+
10+
def assert_lineage(parent, child)
11+
assert_equal parent, child.parent
12+
assert_equal [child, parent], child.self_and_ancestors
13+
14+
# make sure reloading doesn't affect the self_and_ancestors:
15+
child.reload
16+
assert_equal [child, parent], child.self_and_ancestors
17+
end
18+
19+
it 'properly handles schema-qualified table names' do
20+
assert_equal 'test_schema.schema_types', SchemaType.table_name
21+
assert_equal 'test_schema.schema_type_hierarchies', SchemaTypeHierarchy.table_name
22+
end
23+
24+
it 'finds self and parents when children << is used' do
25+
parent = SchemaType.new(name: 'Electronics')
26+
child = SchemaType.new(name: 'Phones')
27+
parent.children << child
28+
parent.save
29+
assert_lineage(parent, child)
30+
end
31+
32+
it 'finds self and parents properly if the constructor is used' do
33+
parent = SchemaType.create(name: 'Electronics')
34+
child = SchemaType.create(name: 'Phones', parent: parent)
35+
assert_lineage(parent, child)
36+
end
37+
38+
it 'creates hierarchy records in the schema-qualified table' do
39+
parent = SchemaType.create!(name: 'Electronics')
40+
child = SchemaType.create!(name: 'Phones', parent: parent)
41+
42+
hierarchy = SchemaTypeHierarchy.where(ancestor_id: parent.id, descendant_id: child.id).first
43+
refute_nil hierarchy
44+
assert_equal 1, hierarchy.generations
45+
end
46+
47+
it 'fixes self_and_ancestors properly on reparenting' do
48+
a = SchemaType.create! name: 'Electronics'
49+
b = SchemaType.create! name: 'Phones'
50+
assert_equal([b], b.self_and_ancestors.to_a)
51+
a.children << b
52+
assert_equal([b, a], b.self_and_ancestors.to_a)
53+
end
54+
55+
it 'supports tree operations with schema-qualified tables' do
56+
root = SchemaType.create!(name: 'Electronics')
57+
child1 = SchemaType.create!(name: 'Computers', parent: root)
58+
child2 = SchemaType.create!(name: 'Phones', parent: root)
59+
grandchild = SchemaType.create!(name: 'Laptops', parent: child1)
60+
61+
assert_equal 2, root.children.count
62+
assert_equal 1, child1.children.count
63+
assert_equal 0, child2.children.count
64+
assert_equal [grandchild, child1, root], grandchild.self_and_ancestors
65+
assert_equal [root, child1, child2, grandchild], root.self_and_descendants.order(:name)
66+
end
67+
end
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
class SchemaType < ApplicationRecord
4+
self.table_name = 'test_schema.schema_types'
5+
has_closure_tree order: :name
6+
7+
def to_s
8+
name
9+
end
10+
end

test/dummy/db/schema.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@
112112
t.integer 'generations', null: false
113113
end
114114

115+
# PostgreSQL schema-qualified tables
116+
execute "CREATE SCHEMA IF NOT EXISTS test_schema"
117+
118+
create_table 'test_schema.schema_types', force: true do |t|
119+
t.string 'name'
120+
t.references 'parent'
121+
end
122+
123+
create_table 'test_schema.schema_type_hierarchies', id: false, force: true do |t|
124+
t.references 'ancestor', null: false
125+
t.references 'descendant', null: false
126+
t.integer 'generations', null: false
127+
end
128+
115129
create_table 'metal' do |t|
116130
t.references 'parent'
117131
t.string 'metal_type'

0 commit comments

Comments
 (0)