From ce0a9cc0ac1c5f392a4a0473b0de344e80c86f64 Mon Sep 17 00:00:00 2001 From: justin talbott Date: Thu, 11 Jan 2024 13:30:35 -0400 Subject: [PATCH] dump inherited table options to schema.rb (#42) * dump inherited table options to schema.rb * fix for no inherited tables * dump inherited tables after * control ordering of dumping * stree --- CHANGELOG.md | 10 +++-- lib/generators/hoardable/install_generator.rb | 12 +++--- lib/hoardable.rb | 2 + lib/hoardable/engine.rb | 10 +++++ lib/hoardable/schema_dumper.rb | 25 ++++++++++++ lib/hoardable/schema_statements.rb | 33 ++++++++++++++++ test/helper.rb | 1 - test/test_schema_dumper.rb | 38 +++++++++++++++++++ 8 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 lib/hoardable/schema_dumper.rb create mode 100644 lib/hoardable/schema_statements.rb create mode 100644 test/test_schema_dumper.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bfa823..9186208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,16 @@ - *Breaking Change* - Support for Ruby 2.7 and Rails 6.1 is dropped - *Breaking Change* - The default scoping clause that controls the inherited table SQL construction - changes from a where clause using `tableoid`s to using `FROM ONLY`. -- fixes an issue for Rails 7.1 regarding accessing version table columns through aliased attributes + changes from a where clause using `tableoid`s to using `FROM ONLY` +- Fixes an issue for Rails 7.1 regarding accessing version table columns through aliased attributes +- Fixes an issue where `Hoardable::RichText` couldn’t be loaded if `ActionText::RichText` wasn’t yet + loaded +- Supports dumping `INHERITS (table_name)` options to `schema.rb` and ensures the inherited tables + are dumped after their parents ## 0.14.3 -- The migration template is updated to make the primary key on the versions table its actual primary key. +- The migration template is updated to make the primary key on the versions table its actual primary key ## 0.14.2 diff --git a/lib/generators/hoardable/install_generator.rb b/lib/generators/hoardable/install_generator.rb index 01bc75e..b831e86 100644 --- a/lib/generators/hoardable/install_generator.rb +++ b/lib/generators/hoardable/install_generator.rb @@ -11,12 +11,12 @@ class InstallGenerator < Rails::Generators::Base def create_initializer_file create_file("config/initializers/hoardable.rb", <<~TEXT) - # Hoardable configuration defaults are below. Learn more at https://github.com/waymondo/hoardable#configuration - # - # Hoardable.enabled = true - # Hoardable.version_updates = true - # Hoardable.save_trash = true - TEXT + # Hoardable configuration defaults are below. Learn more at https://github.com/waymondo/hoardable#configuration + # + # Hoardable.enabled = true + # Hoardable.version_updates = true + # Hoardable.save_trash = true + TEXT end def create_migration_file diff --git a/lib/hoardable.rb b/lib/hoardable.rb index 63fcc6e..55342bd 100644 --- a/lib/hoardable.rb +++ b/lib/hoardable.rb @@ -4,6 +4,8 @@ require "fx" require_relative "hoardable/version" require_relative "hoardable/arel_visitors" +require_relative "hoardable/schema_statements" +require_relative "hoardable/schema_dumper" require_relative "hoardable/engine" require_relative "hoardable/finder_methods" require_relative "hoardable/scopes" diff --git a/lib/hoardable/engine.rb b/lib/hoardable/engine.rb index 176fa66..dddeb41 100644 --- a/lib/hoardable/engine.rb +++ b/lib/hoardable/engine.rb @@ -97,5 +97,15 @@ class Engine < ::Rails::Engine require_relative "encrypted_rich_text" if SUPPORTS_ENCRYPTED_ACTION_TEXT end end + + initializer "hoardable.schema_statements" do + ActiveSupport.on_load(:active_record_postgresqladapter) do + # We need to control the table dumping order of tables, so revert these to just +super+ + Fx::SchemaDumper::Trigger.module_eval("def tables(streams); super; end") + + ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.prepend(SchemaDumper) + ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements.prepend(SchemaStatements) + end + end end end diff --git a/lib/hoardable/schema_dumper.rb b/lib/hoardable/schema_dumper.rb new file mode 100644 index 0000000..e0a4198 --- /dev/null +++ b/lib/hoardable/schema_dumper.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Hoardable + module SchemaDumper + def ignored?(table_name) + super || @connection.inherited_table?(table_name) + end + + def tables(stream) + super + dump_inherited_tables(stream) + empty_line(stream) + triggers(stream) + end + + private def dump_inherited_tables(stream) + sorted_tables = @connection.tables.filter { |table| @connection.inherited_table?(table) }.sort + sorted_tables.each do |table_name| + table(table_name, stream) + foreign_keys(table_name, stream) + end + end + end + private_constant :SchemaDumper +end diff --git a/lib/hoardable/schema_statements.rb b/lib/hoardable/schema_statements.rb new file mode 100644 index 0000000..4743fd1 --- /dev/null +++ b/lib/hoardable/schema_statements.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Hoardable + module SchemaStatements + def table_options(table_name) + options = super || {} + if inherited_table_names = parent_table_names(table_name) + options[:options] = "INHERITS (#{inherited_table_names.join(", ")})" + end + options + end + + private def parent_table_names(table_name) + scope = quoted_scope(table_name, type: "BASE TABLE") + + query_values(<<~SQL.presence, "SCHEMA").presence + SELECT parent.relname + FROM pg_catalog.pg_inherits i + JOIN pg_catalog.pg_class child ON i.inhrelid = child.oid + JOIN pg_catalog.pg_class parent ON i.inhparent = parent.oid + LEFT JOIN pg_namespace n ON n.oid = child.relnamespace + WHERE child.relname = #{scope[:name]} + AND child.relkind IN (#{scope[:type]}) + AND n.nspname = #{scope[:schema]} + SQL + end + + def inherited_table?(table_name) + parent_table_names(table_name).present? + end + end + private_constant :SchemaStatements +end diff --git a/test/helper.rb b/test/helper.rb index 54020b3..57ab65b 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -9,7 +9,6 @@ $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "hoardable" -require "fx" def tmp_dir File.expand_path("../tmp", __dir__) diff --git a/test/test_schema_dumper.rb b/test/test_schema_dumper.rb new file mode 100644 index 0000000..6b3e3f6 --- /dev/null +++ b/test/test_schema_dumper.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "helper" + +class TestSchemaDumper < ActiveSupport::TestCase + test "it dumps table inheritance options to schema.rb" do + output = dump_table_schema("post_versions") + assert_match(<<~SCHEMA.squish, output) + create_table \"post_versions\", + id: :bigint, + default: -> { \"nextval('posts_id_seq'::regclass)\" }, + options: \"INHERITS (posts)\" + SCHEMA + end + + test "it does not dump table inheritance options for non inherited table" do + output = dump_table_schema("posts") + assert_match("create_table \"posts\", force: :cascade do |t|\n", output) + end + + test "it dumps inherited table after parent table, and trigger after both" do + output = dump_table_schema("post_versions", "posts") + assert posts_index = output.index(/create_table "posts"/) + assert post_versions_index = output.index(/create_table "post_versions"/) + assert( + post_versions_trigger_index = output.index(/create_trigger :post_versions_prevent_update/) + ) + assert post_versions_index > posts_index + assert post_versions_trigger_index > post_versions_index + end + + private def dump_table_schema(*table_names) + connection = ActiveRecord::Base.connection + ActiveRecord::SchemaDumper.ignore_tables = connection.data_sources - table_names + stream = StringIO.new + ActiveRecord::SchemaDumper.dump(connection, stream).string + end +end