dynamic_migrations 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/lib/dynamic_migrations/active_record/migrators/column.rb +21 -0
  4. data/lib/dynamic_migrations/active_record/migrators/foreign_key_constraint.rb +112 -0
  5. data/lib/dynamic_migrations/active_record/migrators/function.rb +108 -0
  6. data/lib/dynamic_migrations/active_record/migrators/index.rb +27 -0
  7. data/lib/dynamic_migrations/active_record/migrators/schema.rb +21 -0
  8. data/lib/dynamic_migrations/active_record/migrators/table.rb +21 -0
  9. data/lib/dynamic_migrations/active_record/migrators/trigger.rb +109 -0
  10. data/lib/dynamic_migrations/active_record/migrators/unique_constraint.rb +63 -0
  11. data/lib/dynamic_migrations/active_record/migrators/validation.rb +67 -0
  12. data/lib/dynamic_migrations/active_record/migrators.rb +64 -0
  13. data/lib/dynamic_migrations/name_helper.rb +13 -0
  14. data/lib/dynamic_migrations/postgres/generator/column.rb +92 -0
  15. data/lib/dynamic_migrations/postgres/generator/foreign_key_constraint.rb +84 -0
  16. data/lib/dynamic_migrations/postgres/generator/fragment.rb +30 -0
  17. data/lib/dynamic_migrations/postgres/generator/function.rb +77 -0
  18. data/lib/dynamic_migrations/postgres/generator/index.rb +101 -0
  19. data/lib/dynamic_migrations/postgres/generator/primary_key.rb +55 -0
  20. data/lib/dynamic_migrations/postgres/generator/schema.rb +19 -0
  21. data/lib/dynamic_migrations/postgres/generator/schema_migrations/section.rb +37 -0
  22. data/lib/dynamic_migrations/postgres/generator/schema_migrations.rb +92 -0
  23. data/lib/dynamic_migrations/postgres/generator/table.rb +122 -0
  24. data/lib/dynamic_migrations/postgres/generator/trigger.rb +101 -0
  25. data/lib/dynamic_migrations/postgres/generator/unique_constraint.rb +79 -0
  26. data/lib/dynamic_migrations/postgres/generator/validation.rb +87 -0
  27. data/lib/dynamic_migrations/postgres/generator.rb +359 -0
  28. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +68 -0
  29. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/columns.rb +72 -0
  30. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +73 -0
  31. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +73 -0
  32. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/primary_key.rb +49 -0
  33. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +73 -0
  34. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +73 -0
  35. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +73 -0
  36. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables.rb +80 -0
  37. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas.rb +48 -0
  38. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations.rb +59 -0
  39. data/lib/dynamic_migrations/postgres/server/database/differences.rb +76 -16
  40. data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +35 -9
  41. data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +50 -26
  42. data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +69 -0
  43. data/lib/dynamic_migrations/postgres/server/database/schema/functions.rb +63 -0
  44. data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +6 -44
  45. data/lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb +1 -1
  46. data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +40 -5
  47. data/lib/dynamic_migrations/postgres/server/database/schema/table/index.rb +23 -9
  48. data/lib/dynamic_migrations/postgres/server/database/schema/table/primary_key.rb +21 -6
  49. data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +151 -0
  50. data/lib/dynamic_migrations/postgres/server/database/schema/table/triggers.rb +66 -0
  51. data/lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rb +19 -9
  52. data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +20 -1
  53. data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +15 -5
  54. data/lib/dynamic_migrations/postgres/server/database/schema/tables.rb +63 -0
  55. data/lib/dynamic_migrations/postgres/server/database/schema.rb +3 -49
  56. data/lib/dynamic_migrations/postgres/server/database/source.rb +21 -0
  57. data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +22 -112
  58. data/lib/dynamic_migrations/postgres/server/database/triggers_and_functions_loader.rb +131 -0
  59. data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +10 -4
  60. data/lib/dynamic_migrations/postgres/server/database.rb +2 -1
  61. data/lib/dynamic_migrations/postgres/server.rb +6 -0
  62. data/lib/dynamic_migrations/postgres.rb +1 -1
  63. data/lib/dynamic_migrations/version.rb +1 -1
  64. data/lib/dynamic_migrations.rb +47 -4
  65. metadata +44 -3
  66. data/lib/dynamic_migrations/postgres/data_types.rb +0 -320
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bbdbe66b5d24e600037fcaf794929bca95cb55b6a2bd45de324934ab1ab22b2
4
- data.tar.gz: 06dd757a70e427b753e6f5b3f1bdbf970c749947d13ba7771449f595a2968743
3
+ metadata.gz: 389dc33f29bc39c47961125ad36f5faa377d8609d6d64c2f008517c81bbeb44c
4
+ data.tar.gz: 27bb1d8d55727f8467b52b7240373bfaf397262d88adfa07db2a04a5d3b52e65
5
5
  SHA512:
6
- metadata.gz: c1f8deb5208188c16ff9b6b0071b9fea82111626336cd73d77e519ce93bb1af4df74090c3e34de49d007d1709f7b7041f95cbfca97dc4676e91e74b19b5ee556
7
- data.tar.gz: bfced908cb97de19a678043f6e6fff00faeb11d4dc1ac8b3b15b3e8611f21c121b5ad9a04dae54a8cbf118f9efee9b9462a1b6dcf12904c511d23c15d079ea93
6
+ metadata.gz: bf62099155ca8274632d52fbe7328e66eed962a73cbafd6c53cd056ca8fcea00851ddec52263f47d82ea8486756ea89bf79d03d8b2d2fa489399c7f7e60fb3c0
7
+ data.tar.gz: 0ea7b26061538ae65d1b1c9cddf91dd8afa3a8c2ac3ff41416926235ccb5e0b2ad6cad304129b038d39939cab32492d00c289a85331cc7724a24678b0e202821
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.0.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.2.0...v3.0.0) (2023-08-11)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * functions and triggers, removed unusable index_type from primary key and unique constraints, added active record migrations and some general refactoring
9
+
10
+ ### Features
11
+
12
+ * adding migration generators and active record migrators ([80bf481](https://github.com/craigulliott/dynamic_migrations/commit/80bf481bfd33941b8380e7deb010b171898c03df))
13
+ * functions and triggers, removed unusable index_type from primary key and unique constraints, added active record migrations and some general refactoring ([45fcb7c](https://github.com/craigulliott/dynamic_migrations/commit/45fcb7ca3b6724625a4868198a9e75aefb1ea964))
14
+ * now generating migrations from all database structure object types ([871ab04](https://github.com/craigulliott/dynamic_migrations/commit/871ab048efb6247bc7fadb6e49bb15f6933c5586))
15
+ * various improvements to triggers and functions, and other general improvements ([e7dd9ab](https://github.com/craigulliott/dynamic_migrations/commit/e7dd9abeee736ea2791c192d3f797497f54773b4))
16
+ * work in progress on the migrations generator ([3e8deb9](https://github.com/craigulliott/dynamic_migrations/commit/3e8deb954913be291ad8e6e7d1c09af619378e4d))
17
+ * work in progress on the migrations generator ([7a999ee](https://github.com/craigulliott/dynamic_migrations/commit/7a999ee8467527164e79f2c6da704245e78cf0cd))
18
+
19
+ ## [2.2.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.1.0...v2.2.0) (2023-07-31)
20
+
21
+
22
+ ### Features
23
+
24
+ * using postgres shorthand names such as `numeric(12,2)` for types, and removing the additional column metadata ([86d0113](https://github.com/craigulliott/dynamic_migrations/commit/86d0113aabd4350f278164d18e3c0a611fcf5595))
25
+
3
26
  ## [2.1.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.0.0...v2.1.0) (2023-07-31)
4
27
 
5
28
 
@@ -0,0 +1,21 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Column
5
+ # add a comment to the column
6
+ def set_column_comment table_name, column_name, comment
7
+ execute <<~SQL
8
+ COMMENT ON COLUMN #{schema_name}.#{table_name}.#{column_name} IS '#{quote comment}';
9
+ SQL
10
+ end
11
+
12
+ # remove a column comment
13
+ def remove_column_comment table_name, column_name
14
+ execute <<~SQL
15
+ COMMENT ON COLUMN #{schema_name}.#{table_name}.#{column_name} IS NULL;
16
+ SQL
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,112 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module ForeignKeyConstraint
5
+ class ForeignKeyOnDeleteOptionsError < StandardError
6
+ end
7
+
8
+ class UnexpectedReferentialActionError < StandardError
9
+ end
10
+
11
+ # because rails migrations don't support composite (multiple column) foreign keys
12
+ # column_names and foreign_column_names can be a single column name or
13
+ # an array of column names
14
+ def add_foreign_key table_name, column_names, foreign_schema_name, foreign_table_name, foreign_column_names, name:, initially_deferred: false, deferrable: false, on_delete: :no_action, on_update: :no_action, comment: nil
15
+ if initially_deferred == true && deferrable == false
16
+ raise DeferrableOptionsError, "A constraint can only be initially deferred if it is also deferrable"
17
+ end
18
+
19
+ # convert single column names into arrays, this simplifies the logic below
20
+ column_names = column_names.is_a?(Array) ? column_names : [column_names]
21
+ foreign_column_names = foreign_column_names.is_a?(Array) ? foreign_column_names : [foreign_column_names]
22
+
23
+ # allow it to be deferred, and defer it by default
24
+ deferrable_sql = if initially_deferred
25
+ "DEFERRABLE INITIALLY DEFERRED"
26
+
27
+ # allow it to be deferred, but do not deferr by default
28
+ elsif deferrable
29
+ "DEFERRABLE INITIALLY IMMEDIATE"
30
+
31
+ # it can not be deferred (this is the default)
32
+ else
33
+ "NOT DEFERRABLE"
34
+ end
35
+
36
+ execute <<~SQL
37
+ ALTER TABLE #{table_name}
38
+ ADD CONSTRAINT #{name}
39
+ FOREIGN KEY (#{column_names.join(", ")})
40
+ REFERENCES #{foreign_schema_name}.#{foreign_table_name} (#{foreign_column_names.join(", ")})
41
+ ON DELETE #{referential_action_to_sql on_delete}
42
+ ON UPDATE #{referential_action_to_sql on_update}
43
+ #{deferrable_sql};
44
+ SQL
45
+
46
+ if comment.is_a? String
47
+ set_foreign_key_comment table_name, name, comment
48
+ end
49
+ end
50
+
51
+ def remove_foreign_key table_name, name
52
+ execute <<~SQL
53
+ ALTER TABLE #{table_name}
54
+ DROP CONSTRAINT #{name};
55
+ SQL
56
+ end
57
+
58
+ # add a comment to the foreign_key
59
+ def set_foreign_key_comment table_name, foreign_key_name, comment
60
+ execute <<~SQL
61
+ COMMENT ON CONSTRAINT #{foreign_key_name} ON #{schema_name}.#{table_name} IS '#{quote comment}';
62
+ SQL
63
+ end
64
+
65
+ # remove a foreign_key comment
66
+ def remove_foreign_key_comment table_name, foreign_key_name
67
+ execute <<~SQL
68
+ COMMENT ON CONSTRAINT #{foreign_key_name} ON #{schema_name}.#{table_name} IS NULL;
69
+ SQL
70
+ end
71
+
72
+ private
73
+
74
+ def referential_action_to_sql referential_action
75
+ case referential_action
76
+ # Produce an error indicating that the deletion or update would create a
77
+ # foreign key constraint violation. If the constraint is deferred, this
78
+ # error will be produced at constraint check time if there still exist
79
+ # any referencing rows. This is the default action.
80
+ when :no_action
81
+ "NO ACTION"
82
+
83
+ # Produce an error indicating that the deletion or update would create a
84
+ # foreign key constraint violation. This is the same as NO ACTION except
85
+ # that the check is not deferrable.
86
+ when :restrict
87
+ "RESTRICT"
88
+
89
+ # Delete any rows referencing the deleted row, or update the values of
90
+ # the referencing column(s) to the new values of the referenced columns,
91
+ # respectively.
92
+ when :cascade
93
+ "CASCADE"
94
+
95
+ # Set all of the referencing columns, or a specified subset of the
96
+ # referencing columns, to null.
97
+ when :set_null
98
+ "SET NULL"
99
+
100
+ # Set all of the referencing columns, or a specified subset of the
101
+ # referencing columns, to their default values.
102
+ when :set_default
103
+ "SET DEFAULT"
104
+
105
+ else
106
+ raise UnexpectedReferentialActionError, referential_action
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,108 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Function
5
+ class FunctionDoesNotExistError < StandardError
6
+ end
7
+
8
+ class MissingFunctionBlockError < StandardError
9
+ end
10
+
11
+ # create a postgres function
12
+ def create_function table_name, function_name, comment: nil, &block
13
+ unless block
14
+ raise MissingFunctionBlockError, "create_function requires a block"
15
+ end
16
+ # todo - remove this once steep/rbs can better handle blocks
17
+ unless block.is_a? NilClass
18
+ fn_sql = block.call.strip
19
+ end
20
+
21
+ # ensure that the function ends with a semicolon
22
+ unless fn_sql.end_with? ";"
23
+ fn_sql << ";"
24
+ end
25
+
26
+ # schema_name was not provided to this method, it comes from the migration class
27
+ execute <<~SQL
28
+ CREATE FUNCTION #{schema_name}.#{function_name}() returns trigger language plpgsql AS
29
+ $$BEGIN #{fn_sql}
30
+ RETURN NEW;
31
+ END$$;
32
+ SQL
33
+
34
+ if comment.is_a? String
35
+ set_function_comment function_name, comment
36
+ end
37
+ end
38
+
39
+ # update a postgres function
40
+ def update_function table_name, function_name, comment: nil, &block
41
+ unless block
42
+ raise MissingFunctionBlockError, "create_function requires a block"
43
+ end
44
+ # todo - remove this once steep/rbs can better handle blocks
45
+ unless block.is_a? NilClass
46
+ fn_sql = block.call.strip
47
+ end
48
+
49
+ # ensure that the function ends with a semicolon
50
+ unless fn_sql.end_with? ";"
51
+ fn_sql << ";"
52
+ end
53
+
54
+ # schema_name was not provided to this method, it comes from the migration class
55
+ # assert it already exists
56
+ exists_result = execute <<~SQL
57
+ SELECT TRUE as exists
58
+ FROM pg_proc p
59
+ INNER JOIN pg_namespace p_n
60
+ ON p_n.oid = p.pronamespace
61
+ WHERE
62
+ p.proname = #{function_name}
63
+ AND p_n.nspname = #{schema_name}
64
+ -- arguments (defaulting to none for now)
65
+ AND pg_get_function_identity_arguments(p.oid) = ''
66
+ SQL
67
+
68
+ unless exists_result.to_a.first["exists"]
69
+ raise FunctionDoesNotExistError, "Can not update Function. Function #{schema_name}.#{function_name} does not exist."
70
+ end
71
+
72
+ # create or replace will update the function
73
+ execute <<~SQL
74
+ CREATE OR REPLACE FUNCTION #{schema_name}.#{function_name}() returns trigger language plpgsql AS
75
+ $$BEGIN #{fn_sql}
76
+ RETURN NEW;
77
+ END$$;
78
+ SQL
79
+
80
+ if comment.is_a? String
81
+ set_function_comment function_name, comment
82
+ end
83
+ end
84
+
85
+ # remove a function from the schema
86
+ def drop_function function_name
87
+ execute <<~SQL
88
+ DROP FUNCTION #{schema_name}.#{function_name}();
89
+ SQL
90
+ end
91
+
92
+ # add a comment to a function
93
+ def set_function_comment function_name, comment
94
+ execute <<~SQL
95
+ COMMENT ON FUNCTION #{schema_name}.#{function_name} IS '#{quote comment}';
96
+ SQL
97
+ end
98
+
99
+ # remove the comment from a function
100
+ def remove_function_comment function_name
101
+ execute <<~SQL
102
+ COMMENT ON FUNCTION #{schema_name}.#{function_name} IS null;
103
+ SQL
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,27 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Index
5
+ # add a comment to the index
6
+ # table name is not needed, but is included here for consistency with the other
7
+ # rils migrations methods, and to make it easier to understand what table this
8
+ # index is relate to
9
+ def set_index_comment table_name, index_name, comment
10
+ execute <<~SQL
11
+ COMMENT ON INDEX #{index_name} IS '#{quote comment}';
12
+ SQL
13
+ end
14
+
15
+ # remove a index comment
16
+ # table name is not needed, but is included here for consistency with the other
17
+ # rils migrations methods, and to make it easier to understand what table this
18
+ # index is relate to
19
+ def remove_index_comment table_name, index_name
20
+ execute <<~SQL
21
+ COMMENT ON INDEX #{index_name} IS NULL;
22
+ SQL
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Schema
5
+ def create_schema
6
+ # schema_name was not provided to this method, it comes from the migration class
7
+ execute <<~SQL
8
+ CREATE SCHEMA #{schema_name};
9
+ SQL
10
+ end
11
+
12
+ def drop_schema
13
+ # schema_name was not provided to this method, it comes from the migration class
14
+ execute <<~SQL
15
+ DROP SCHEMA #{schema_name};
16
+ SQL
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Table
5
+ # add a comment to the table
6
+ def set_table_comment table_name, comment
7
+ execute <<~SQL
8
+ COMMENT ON TABLE #{schema_name}.#{table_name} IS '#{quote comment}';
9
+ SQL
10
+ end
11
+
12
+ # remove a table comment
13
+ def remove_table_comment table_name
14
+ execute <<~SQL
15
+ COMMENT ON TABLE #{schema_name}.#{table_name} IS NULL;
16
+ SQL
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Trigger
5
+ # create a trigger
6
+ class UnexpectedEventManipulationError < StandardError
7
+ end
8
+
9
+ class UnexpectedActionOrientationError < StandardError
10
+ end
11
+
12
+ class UnexpectedActionTimingError < StandardError
13
+ end
14
+
15
+ class UnexpectedConditionsError < StandardError
16
+ end
17
+
18
+ # create a postgres trigger
19
+ def add_trigger table_name, name:, action_timing:, event_manipulation:, action_orientation:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
20
+ unless [:insert, :delete, :update].include? event_manipulation
21
+ raise UnexpectedEventManipulationError, event_manipulation
22
+ end
23
+
24
+ unless action_condition.nil? || action_condition.is_a?(String)
25
+ raise UnexpectedConditionsError, "expected String but got `#{action_condition}`"
26
+ end
27
+
28
+ unless [:row, :statement].include? action_orientation
29
+ raise UnexpectedActionOrientationError, action_orientation
30
+ end
31
+
32
+ unless [:before, :after, :instead_of].include? action_timing
33
+ raise UnexpectedActionTimingError, action_timing
34
+ end
35
+
36
+ # "INSTEAD OF/BEFORE/AFTER" "INSERT/UPDATE/DELETE"
37
+ timing_sql = "#{action_timing.to_s.sub("_", " ")} #{event_manipulation}".upcase
38
+
39
+ condition_sql = action_condition.nil? ? "" : "WHEN (#{action_condition})"
40
+
41
+ temp_tables = []
42
+ unless action_reference_old_table.nil?
43
+ temp_tables << "OLD TABLE AS #{action_reference_old_table}"
44
+ end
45
+ unless action_reference_new_table.nil?
46
+ temp_tables << "NEW TABLE AS #{action_reference_new_table}"
47
+ end
48
+ temp_tables_sql = temp_tables.any? ? "REFERENCING #{temp_tables.join(" ")}" : ""
49
+
50
+ # schema_name was not provided to this method, it comes from the migration class
51
+ execute <<~SQL
52
+ CREATE TRIGGER #{name}
53
+ #{timing_sql} ON #{schema_name}.#{table_name} #{temp_tables_sql}
54
+ FOR EACH #{action_orientation}
55
+ #{condition_sql}
56
+ EXECUTE FUNCTION #{function_schema_name}.#{function_name}();
57
+ SQL
58
+
59
+ if comment.is_a? String
60
+ set_trigger_comment table_name, name, comment
61
+ end
62
+ end
63
+
64
+ # wrappers for add_trigger which provide more friendly syntax
65
+ def before_insert table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
66
+ add_trigger table_name, name: name, action_timing: :before, event_manipulation: :insert, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
67
+ end
68
+
69
+ def before_update table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
70
+ add_trigger table_name, name: name, action_timing: :before, event_manipulation: :update, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
71
+ end
72
+
73
+ def before_delete table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
74
+ add_trigger table_name, name: name, action_timing: :before, event_manipulation: :delete, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
75
+ end
76
+
77
+ def after_insert table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
78
+ add_trigger table_name, name: name, action_timing: :after, event_manipulation: :insert, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
79
+ end
80
+
81
+ def after_update table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
82
+ add_trigger table_name, name: name, action_timing: :after, event_manipulation: :update, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
83
+ end
84
+
85
+ def after_delete table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
86
+ add_trigger table_name, name: name, action_timing: :after, event_manipulation: :delete, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
87
+ end
88
+
89
+ def remove_trigger table_name, trigger_name
90
+ execute <<~SQL
91
+ DROP TRIGGER #{trigger_name} ON #{schema_name}.#{table_name};
92
+ SQL
93
+ end
94
+
95
+ def set_trigger_comment table_name, trigger_name, comment
96
+ execute <<~SQL
97
+ COMMENT ON TRIGGER #{trigger_name} ON #{schema_name}.#{table_name} IS '#{quote comment}';
98
+ SQL
99
+ end
100
+
101
+ def remove_trigger_comment table_name, trigger_name
102
+ execute <<~SQL
103
+ COMMENT ON TRIGGER #{trigger_name} ON #{schema_name}.#{table_name} IS NULL;
104
+ SQL
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,63 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module UniqueConstraint
5
+ # because rails migrations don't support composite (multiple column) foreign keys
6
+ # column_names can be a single column name or an array of column names
7
+ def add_unique_constraint table_name, column_names, name:, deferrable: false, initially_deferred: false, comment: nil
8
+ if initially_deferred == true && deferrable == false
9
+ raise DeferrableOptionsError, "A constraint can only be initially deferred if it is also deferrable"
10
+ end
11
+
12
+ # convert single column names into arrays, this simplifies the logic below
13
+ column_names = column_names.is_a?(Array) ? column_names : [column_names]
14
+
15
+ # allow it to be deferred, and defer it by default
16
+ deferrable_sql = if initially_deferred
17
+ "DEFERRABLE INITIALLY DEFERRED"
18
+
19
+ # allow it to be deferred, but do not deferr by default
20
+ elsif deferrable
21
+ "DEFERRABLE INITIALLY IMMEDIATE"
22
+
23
+ # it can not be deferred (this is the default)
24
+ else
25
+ "NOT DEFERRABLE"
26
+ end
27
+
28
+ execute <<~SQL
29
+ ALTER TABLE #{table_name}
30
+ ADD CONSTRAINT #{name}
31
+ UNIQUE (#{column_names.join(", ")})
32
+ #{deferrable_sql};
33
+ SQL
34
+
35
+ if comment.is_a? String
36
+ set_unique_constraint_comment table_name, name, comment
37
+ end
38
+ end
39
+
40
+ def remove_unique_constraint table_name, name
41
+ execute <<~SQL
42
+ ALTER TABLE #{table_name}
43
+ DROP CONSTRAINT #{name};
44
+ SQL
45
+ end
46
+
47
+ # add a comment to the unique_constraint
48
+ def set_unique_constraint_comment table_name, unique_constraint_name, comment
49
+ execute <<~SQL
50
+ COMMENT ON CONSTRAINT #{unique_constraint_name} ON #{schema_name}.#{table_name} IS '#{quote comment}';
51
+ SQL
52
+ end
53
+
54
+ # remove a unique_constraint comment
55
+ def remove_unique_constraint_comment table_name, unique_constraint_name
56
+ execute <<~SQL
57
+ COMMENT ON CONSTRAINT #{unique_constraint_name} ON #{schema_name}.#{table_name} IS NULL;
58
+ SQL
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,67 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Validation
5
+ # this exists because because the standard rails migration does not support deffered constraints
6
+ def add_validation table_name, name:, initially_deferred: false, deferrable: false, comment: nil, &block
7
+ unless block
8
+ raise MissingFunctionBlockError, "create_function requires a block"
9
+ end
10
+ # todo - remove this once steep/rbs can better handle blocks
11
+ unless block.is_a? NilClass
12
+ sql = block.call.strip
13
+ end
14
+
15
+ if initially_deferred == true && deferrable == false
16
+ raise DeferrableOptionsError, "A constraint can only be initially deferred if it is also deferrable"
17
+ end
18
+
19
+ # allow it to be deferred, and defer it by default
20
+ deferrable_sql = if initially_deferred
21
+ "DEFERRABLE INITIALLY DEFERRED"
22
+
23
+ # allow it to be deferred, but do not deferr by default
24
+ elsif deferrable
25
+ "DEFERRABLE INITIALLY IMMEDIATE"
26
+
27
+ # it can not be deferred (this is the default)
28
+ else
29
+ "NOT DEFERRABLE"
30
+ end
31
+
32
+ execute <<~SQL
33
+ ALTER TABLE #{table_name}
34
+ ADD CONSTRAINT #{name}
35
+ CHECK (#{sql})
36
+ #{deferrable_sql};
37
+ SQL
38
+
39
+ if comment.is_a? String
40
+ set_validation_comment table_name, name, comment
41
+ end
42
+ end
43
+
44
+ def remove_validation table_name, name
45
+ execute <<~SQL
46
+ ALTER TABLE #{table_name}
47
+ DROP CONSTRAINT #{name};
48
+ SQL
49
+ end
50
+
51
+ # add a comment to the validation
52
+ def set_validation_comment table_name, validation_name, comment
53
+ execute <<~SQL
54
+ COMMENT ON CONSTRAINT #{validation_name} ON #{schema_name}.#{table_name} IS '#{quote comment}';
55
+ SQL
56
+ end
57
+
58
+ # remove a validation comment
59
+ def remove_validation_comment table_name, validation_name
60
+ execute <<~SQL
61
+ COMMENT ON CONSTRAINT #{validation_name} ON #{schema_name}.#{table_name} IS NULL;
62
+ SQL
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,64 @@
1
+ # include this module at the top of your migration to get access to the
2
+ # custom migration methods. For example:
3
+ #
4
+ # class CreateFooBars < ActiveRecord::Migration[7.0]
5
+ # include DynamicMigrations::ActiveRecord::Migrators
6
+ #
7
+ # def change
8
+ # ...
9
+ # end
10
+ # end
11
+ #
12
+ module DynamicMigrations
13
+ module ActiveRecord
14
+ module Migrators
15
+ class SchemaNameNotSetError < StandardError
16
+ end
17
+
18
+ class DeferrableOptionsError < StandardError
19
+ end
20
+
21
+ class MissingFunctionBlockError < StandardError
22
+ end
23
+
24
+ include Schema
25
+ include Validation
26
+ include ForeignKeyConstraint
27
+ include Table
28
+ include Index
29
+ include Column
30
+ include Function
31
+ include UniqueConstraint
32
+ include Trigger
33
+
34
+ # The schema name should be set before the migrations for
35
+ # each schema's migrations are run. This is done by:
36
+ # DynamicMigrations::ActiveRecord::Migrators.set_schema_name :schema_name
37
+ def self.schema_name
38
+ @current_schema
39
+ end
40
+
41
+ def self.set_schema_name schema_name
42
+ @current_schema = schema_name.to_sym
43
+ end
44
+
45
+ def self.clear_schema_name
46
+ @current_schema = nil
47
+ end
48
+
49
+ def quote string
50
+ connection.quote string
51
+ end
52
+
53
+ # this method is made available on the final migration class
54
+ def schema_name
55
+ sn = Migrators.schema_name
56
+ if sn.nil?
57
+ raise SchemaNameNotSetError
58
+ end
59
+ # return the schema name
60
+ sn
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,13 @@
1
+ module DynamicMigrations
2
+ module NameHelper
3
+ # shortens a table name like 'invoice_subscription_prepayments' to 'inv_sub_pre'
4
+ warn "no unit tests"
5
+ def abbreviate_table_name table_name
6
+ table_name_without_schema = table_name.to_s.split(".").last
7
+ if table_name_without_schema.nil?
8
+ raise "no table name provided"
9
+ end
10
+ table_name_without_schema.split("_").map { |v| v[0..2] }.join("_")
11
+ end
12
+ end
13
+ end