diff --git a/.tool-versions b/.tool-versions index ca745c6d..3f03c7a7 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.4.4 +ruby 3.4.7 diff --git a/lib/closure_tree/support.rb b/lib/closure_tree/support.rb index 9faa9b5a..c13676d0 100644 --- a/lib/closure_tree/support.rb +++ b/lib/closure_tree/support.rb @@ -51,8 +51,19 @@ def hierarchy_table_name # We need to use the table_name, not something like ct_class.to_s.demodulize + "_hierarchies", # because they may have overridden the table name, which is what we want to be consistent with # in order for the schema to make sense. - tablename = options[:hierarchy_table_name] || - "#{remove_prefix_and_suffix(table_name, model_class).singularize}_hierarchies" + if options[:hierarchy_table_name] + tablename = options[:hierarchy_table_name] + else + base_table = remove_prefix_and_suffix(table_name, model_class) + + # Handle PostgreSQL schema-qualified table names (e.g., "my_schema.table_name") + schema, _, table = base_table.rpartition('.') + if schema.present? + tablename = "#{schema}.#{table.singularize}_hierarchies" + else + tablename = "#{table.singularize}_hierarchies" + end + end [model_class.table_name_prefix, tablename, model_class.table_name_suffix].join end diff --git a/test/closure_tree/schema_type_test.rb b/test/closure_tree/schema_type_test.rb new file mode 100644 index 00000000..3480581e --- /dev/null +++ b/test/closure_tree/schema_type_test.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'test_helper' + +describe SchemaType do + before do + skip 'PostgreSQL only' unless postgresql? + end + + def assert_lineage(parent, child) + assert_equal parent, child.parent + assert_equal [child, parent], child.self_and_ancestors + + # make sure reloading doesn't affect the self_and_ancestors: + child.reload + assert_equal [child, parent], child.self_and_ancestors + end + + it 'properly handles schema-qualified table names' do + assert_equal 'test_schema.schema_types', SchemaType.table_name + assert_equal 'test_schema.schema_type_hierarchies', SchemaTypeHierarchy.table_name + end + + it 'finds self and parents when children << is used' do + parent = SchemaType.new(name: 'Electronics') + child = SchemaType.new(name: 'Phones') + parent.children << child + parent.save + assert_lineage(parent, child) + end + + it 'finds self and parents properly if the constructor is used' do + parent = SchemaType.create(name: 'Electronics') + child = SchemaType.create(name: 'Phones', parent: parent) + assert_lineage(parent, child) + end + + it 'creates hierarchy records in the schema-qualified table' do + parent = SchemaType.create!(name: 'Electronics') + child = SchemaType.create!(name: 'Phones', parent: parent) + + hierarchy = SchemaTypeHierarchy.where(ancestor_id: parent.id, descendant_id: child.id).first + refute_nil hierarchy + assert_equal 1, hierarchy.generations + end + + it 'fixes self_and_ancestors properly on reparenting' do + a = SchemaType.create! name: 'Electronics' + b = SchemaType.create! name: 'Phones' + assert_equal([b], b.self_and_ancestors.to_a) + a.children << b + assert_equal([b, a], b.self_and_ancestors.to_a) + end + + it 'supports tree operations with schema-qualified tables' do + root = SchemaType.create!(name: 'Electronics') + child1 = SchemaType.create!(name: 'Computers', parent: root) + child2 = SchemaType.create!(name: 'Phones', parent: root) + grandchild = SchemaType.create!(name: 'Laptops', parent: child1) + + assert_equal 2, root.children.count + assert_equal 1, child1.children.count + assert_equal 0, child2.children.count + assert_equal [grandchild, child1, root], grandchild.self_and_ancestors + assert_equal [root, child1, child2, grandchild], root.self_and_descendants.order(:name) + end +end diff --git a/test/dummy/app/models/schema_type.rb b/test/dummy/app/models/schema_type.rb new file mode 100644 index 00000000..76fc99c0 --- /dev/null +++ b/test/dummy/app/models/schema_type.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class SchemaType < ApplicationRecord + self.table_name = 'test_schema.schema_types' + has_closure_tree order: :name + + def to_s + name + end +end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 6994077c..8884f4f2 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -112,6 +112,20 @@ t.integer 'generations', null: false end + # PostgreSQL schema-qualified tables + execute "CREATE SCHEMA IF NOT EXISTS test_schema" + + create_table 'test_schema.schema_types', force: true do |t| + t.string 'name' + t.references 'parent' + end + + create_table 'test_schema.schema_type_hierarchies', id: false, force: true do |t| + t.references 'ancestor', null: false + t.references 'descendant', null: false + t.integer 'generations', null: false + end + create_table 'metal' do |t| t.references 'parent' t.string 'metal_type'