dynamic_migrations 2.2.0 → 3.1.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -4
  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 +81 -6
  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 +49 -8
  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 +4 -0
  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 +6 -6
  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 -3
  65. metadata +44 -2
@@ -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
@@ -0,0 +1,92 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module Column
5
+ class NoColumnCommentError < StandardError
6
+ end
7
+
8
+ def add_column column, code_comment = nil
9
+ if column.description.nil?
10
+ raise NoColumnCommentError, "Refusing to generate add_column migration, no description was provided for `#{column.table.schema.name}`.`#{column.table.name}` column `#{column.name}`"
11
+ end
12
+
13
+ options = {}
14
+ options[:null] = column.null
15
+
16
+ unless column.default.nil?
17
+ options[:default] = "\"#{column.default}\""
18
+ end
19
+
20
+ if column.array?
21
+ options[:array] = true
22
+ end
23
+
24
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
25
+
26
+ data_type = column.data_type
27
+ if column.array?
28
+ data_type = "\"#{data_type}\""
29
+ end
30
+
31
+ add_migration column.table.schema.name, column.table.name, :add_column, column.name, code_comment, <<~RUBY
32
+ add_column :#{column.table.name}, :#{column.name}, :#{data_type}, #{options_syntax}, comment: <<~COMMENT
33
+ #{indent column.description}
34
+ COMMENT
35
+ RUBY
36
+ end
37
+
38
+ def change_column column, code_comment = nil
39
+ options = {}
40
+ options[:null] = column.null
41
+
42
+ unless column.default.nil?
43
+ options[:default] = "\"#{column.default}\""
44
+ end
45
+
46
+ if column.array?
47
+ options[:array] = true
48
+ end
49
+
50
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
51
+
52
+ data_type = column.data_type
53
+ if column.array?
54
+ data_type = ":\"#{data_type}\""
55
+ end
56
+
57
+ add_migration column.table.schema.name, column.table.name, :change_column, column.name, code_comment, <<~RUBY
58
+ change_column :#{column.table.name}, :#{column.name}, :#{data_type}, #{options_syntax}
59
+ RUBY
60
+ end
61
+
62
+ def remove_column column, code_comment = nil
63
+ add_migration column.table.schema.name, column.table.name, :remove_column, column.name, code_comment, <<~RUBY
64
+ remove_column :#{column.table.name}, :#{column.name}
65
+ RUBY
66
+ end
67
+
68
+ # add a comment to a column
69
+ def set_column_comment column, code_comment = nil
70
+ description = column.description
71
+
72
+ if description.nil?
73
+ raise MissingDescriptionError
74
+ end
75
+
76
+ add_migration column.table.schema.name, column.table.name, :set_column_comment, column.name, code_comment, <<~RUBY
77
+ set_column_comment :#{column.table.name}, :#{column.name}, <<~COMMENT
78
+ #{indent description}
79
+ COMMENT
80
+ RUBY
81
+ end
82
+
83
+ # remove the comment from a column
84
+ def remove_column_comment column, code_comment = nil
85
+ add_migration column.table.schema.name, column.table.name, :remove_column_comment, column.name, code_comment, <<~RUBY
86
+ remove_column_comment :#{column.table.name}, :#{column.name}
87
+ RUBY
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,84 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module ForeignKeyConstraint
5
+ def add_foreign_key_constraint foreign_key_constraint, code_comment = nil
6
+ # the migration accepts either a single column name or an array of column names
7
+ # we use the appropriate syntax just to make the migration prettier and easier
8
+ # to understand
9
+ p_c_names = foreign_key_constraint.column_names
10
+ column_names = (p_c_names.count == 1) ? ":#{p_c_names.first}" : "[:#{p_c_names.join(", :")}]"
11
+
12
+ f_c_names = foreign_key_constraint.foreign_column_names
13
+ foreign_column_names = (f_c_names.count == 1) ? ":#{f_c_names.first}" : "[:#{f_c_names.join(", :")}]"
14
+
15
+ options = {
16
+ name: ":#{foreign_key_constraint.name}",
17
+ initially_deferred: foreign_key_constraint.initially_deferred,
18
+ deferrable: foreign_key_constraint.deferrable,
19
+ on_delete: ":#{foreign_key_constraint.on_delete}",
20
+ on_update: ":#{foreign_key_constraint.on_update}"
21
+ }
22
+ unless foreign_key_constraint.description.nil?
23
+ options[:comment] = <<~RUBY
24
+ <<~COMMENT
25
+ #{indent foreign_key_constraint.description}
26
+ COMMENT
27
+ RUBY
28
+ end
29
+
30
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
31
+
32
+ add_migration foreign_key_constraint.table.schema.name, foreign_key_constraint.table.name, :add_foreign_key, foreign_key_constraint.name, code_comment, <<~RUBY
33
+ add_foreign_key :#{foreign_key_constraint.table.name}, #{column_names}, :#{foreign_key_constraint.foreign_table.name}, #{foreign_column_names}, #{options_syntax}
34
+ RUBY
35
+ end
36
+
37
+ def remove_foreign_key_constraint foreign_key_constraint, code_comment = nil
38
+ add_migration foreign_key_constraint.table.schema.name, foreign_key_constraint.table.name, :remove_foreign_key, foreign_key_constraint.name, code_comment, <<~RUBY
39
+ remove_foreign_key :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}
40
+ RUBY
41
+ end
42
+
43
+ def recreate_foreign_key_constraint original_foreign_key_constraint, updated_foreign_key_constraint
44
+ # remove the original foreign_key_constraint
45
+ removal_fragment = remove_foreign_key_constraint original_foreign_key_constraint, <<~CODE_COMMENT
46
+ Removing original foreign key constraint because it has changed (it is recreated below)
47
+ Changes:
48
+ #{indent original_foreign_key_constraint.differences_descriptions(updated_foreign_key_constraint).join("\n")}
49
+ CODE_COMMENT
50
+
51
+ # recrete the foreign_key_constraint with the new options
52
+ recreation_fragment = add_foreign_key_constraint updated_foreign_key_constraint, <<~CODE_COMMENT
53
+ Recreating this foreign key constraint
54
+ CODE_COMMENT
55
+
56
+ # return the new fragments (the main reason to return them here is for the specs)
57
+ [removal_fragment, recreation_fragment]
58
+ end
59
+
60
+ # add a comment to a foreign_key_constraint
61
+ def set_foreign_key_constraint_comment foreign_key_constraint, code_comment = nil
62
+ description = foreign_key_constraint.description
63
+
64
+ if description.nil?
65
+ raise MissingDescriptionError
66
+ end
67
+
68
+ add_migration foreign_key_constraint.table.schema.name, foreign_key_constraint.table.name, :set_foreign_key_constraint_comment, foreign_key_constraint.name, code_comment, <<~RUBY
69
+ set_foreign_key_comment :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}, <<~COMMENT
70
+ #{indent description}
71
+ COMMENT
72
+ RUBY
73
+ end
74
+
75
+ # remove the comment from a foreign_key_constraint
76
+ def remove_foreign_key_constraint_comment foreign_key_constraint, code_comment = nil
77
+ add_migration foreign_key_constraint.table.schema.name, foreign_key_constraint.table.name, :remove_foreign_key_constraint_comment, foreign_key_constraint.name, code_comment, <<~RUBY
78
+ remove_foreign_key_comment :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}
79
+ RUBY
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,30 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ class Fragment
5
+ attr_reader :object_name
6
+ attr_reader :code_comment
7
+
8
+ def initialize object_name, code_comment, content
9
+ @object_name = object_name
10
+ @code_comment = code_comment
11
+ @content = content
12
+ end
13
+
14
+ def to_s
15
+ strings = []
16
+ comment = @code_comment
17
+ unless comment.nil?
18
+ strings << "# " + comment.split("\n").join("\n# ")
19
+ end
20
+ strings << @content
21
+ strings.join("\n").strip
22
+ end
23
+
24
+ def has_code_comment?
25
+ !@code_comment.nil?
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,77 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module Function
5
+ def create_function function, code_comment = nil
6
+ options = {}
7
+
8
+ if function.description.nil?
9
+ comment_sql = ""
10
+ else
11
+ comment_sql = <<~RUBY
12
+ #{function.name}_comment = <<~COMMENT
13
+ #{indent function.description || ""}
14
+ COMMENT
15
+ RUBY
16
+ options[:comment] = "#{function.name}_comment"
17
+ end
18
+
19
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
20
+ optional_options_syntax = (options_syntax == "") ? "" : ", #{options_syntax}"
21
+
22
+ fn_sql = function.definition.strip
23
+ # ensure that the function ends with a semicolon
24
+ unless fn_sql.end_with? ";"
25
+ fn_sql << ";"
26
+ end
27
+
28
+ add_migration function.schema.name, function.triggers.first&.table&.name, :create_function, function.name, code_comment, (comment_sql + <<~RUBY)
29
+ create_function :#{function.name}#{optional_options_syntax} do
30
+ <<~SQL
31
+ #{indent fn_sql}
32
+ SQL
33
+ end
34
+ RUBY
35
+ end
36
+
37
+ def update_function function, code_comment = nil
38
+ fn_sql = function.definition.strip
39
+ # ensure that the function ends with a semicolon
40
+ unless fn_sql.end_with? ";"
41
+ fn_sql << ";"
42
+ end
43
+
44
+ add_migration function.schema.name, function.triggers.first&.table&.name, :update_function, function.name, code_comment, <<~RUBY
45
+ update_function :#{function.name} do
46
+ <<~SQL
47
+ #{indent fn_sql}
48
+ SQL
49
+ end
50
+ RUBY
51
+ end
52
+
53
+ def drop_function function, code_comment = nil
54
+ add_migration function.schema.name, function.triggers.first&.table&.name, :drop_function, function.name, code_comment, <<~RUBY
55
+ drop_function :#{function.name}
56
+ RUBY
57
+ end
58
+
59
+ # add a comment to a function
60
+ def set_function_comment function, code_comment = nil
61
+ add_migration function.schema.name, function.triggers.first&.table&.name, :set_function_comment, function.name, code_comment, <<~RUBY
62
+ set_function_comment :#{function.name}, <<~COMMENT
63
+ #{indent function.description || ""}
64
+ COMMENT
65
+ RUBY
66
+ end
67
+
68
+ # remove the comment from a function
69
+ def remove_function_comment function, code_comment = nil
70
+ add_migration function.schema.name, function.triggers.first&.table&.name, :remove_function_comment, function.name, code_comment, <<~RUBY
71
+ remove_function_comment :#{function.name}
72
+ RUBY
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,101 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module Index
5
+ def add_index index, code_comment = nil
6
+ # the migration accepts either a single column name or an array of column names
7
+ # we use the appropriate syntax just to make the migration prettier and easier
8
+ # to understand
9
+ column_names = (index.column_names.count == 1) ? ":#{index.column_names.first}" : "[:#{index.column_names.join(", :")}]"
10
+
11
+ options = {
12
+ name: ":#{index.name}",
13
+ unique: index.unique,
14
+ using: ":#{index.type}",
15
+ # todo: support custom sorting, it requires refactoring the index class because the ordering is actually on a column by column basis, not the index itself
16
+ sort: ":#{index.order}"
17
+ }
18
+
19
+ # :first is the default when :desc is specified, :last is the default when :asc is specified
20
+ if (index.order == :desc && index.nulls_position == :last) || (index.order == :asc && index.nulls_position == :first)
21
+ # todo: support nulls_position, it requires writing our own migrator because rails does not provide this option
22
+ raise "custom nulls_position is not currently supported"
23
+ end
24
+
25
+ unless index.where.nil?
26
+ options[:where] = "\"#{index.where}\""
27
+ end
28
+
29
+ unless index.description.nil?
30
+ options[:comment] = <<~RUBY
31
+ <<~COMMENT
32
+ #{indent index.description}
33
+ COMMENT
34
+ RUBY
35
+ end
36
+
37
+ where_sql = ""
38
+ unless index.where.nil?
39
+ options[:where] = "#{index.name}_where_sql"
40
+ where_sql = <<~RUBY
41
+ #{index.name}_where_sql = <<~SQL
42
+ #{indent index.where}
43
+ SQL
44
+ RUBY
45
+ end
46
+
47
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
48
+
49
+ add_migration index.table.schema.name, index.table.name, :add_index, index.name, code_comment, (where_sql + <<~RUBY)
50
+ add_index :#{index.table.name}, #{column_names}, #{options_syntax}
51
+ RUBY
52
+ end
53
+
54
+ def remove_index index, code_comment = nil
55
+ add_migration index.table.schema.name, index.table.name, :remove_index, index.name, code_comment, <<~RUBY
56
+ remove_index :#{index.table.name}, :#{index.name}
57
+ RUBY
58
+ end
59
+
60
+ def recreate_index original_index, updated_index
61
+ # remove the original index
62
+ removal_fragment = remove_index original_index, <<~CODE_COMMENT
63
+ Removing original index because it has changed (it is recreated below)
64
+ Changes:
65
+ #{indent original_index.differences_descriptions(updated_index).join("\n")}
66
+ CODE_COMMENT
67
+
68
+ # recrete the index with the new options
69
+ recreation_fragment = add_index updated_index, <<~CODE_COMMENT
70
+ Recreating this index
71
+ CODE_COMMENT
72
+
73
+ # return the new fragments (the main reason to return them here is for the specs)
74
+ [removal_fragment, recreation_fragment]
75
+ end
76
+
77
+ # add a comment to a index
78
+ def set_index_comment index, code_comment = nil
79
+ description = index.description
80
+
81
+ if description.nil?
82
+ raise MissingDescriptionError
83
+ end
84
+
85
+ add_migration index.table.schema.name, index.table.name, :set_index_comment, index.name, code_comment, <<~RUBY
86
+ set_index_comment :#{index.table.name}, :#{index.name}, <<~COMMENT
87
+ #{indent description}
88
+ COMMENT
89
+ RUBY
90
+ end
91
+
92
+ # remove the comment from a index
93
+ def remove_index_comment index, code_comment = nil
94
+ add_migration index.table.schema.name, index.table.name, :remove_index_comment, index.name, code_comment, <<~RUBY
95
+ remove_index_comment :#{index.table.name}, :#{index.name}
96
+ RUBY
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,55 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module PrimaryKey
5
+ def add_primary_key primary_key, code_comment = nil
6
+ # the migration accepts either a single column name or an array of column names
7
+ # we use the appropriate syntax just to make the migration prettier and easier
8
+ # to understand
9
+ column_names = (primary_key.column_names.count == 1) ? ":#{primary_key.column_names.first}" : "[:#{primary_key.column_names.join(", :")}]"
10
+
11
+ options = {
12
+ name: ":#{primary_key.name}"
13
+ }
14
+
15
+ unless primary_key.description.nil?
16
+ options[:comment] = <<~RUBY
17
+ <<~COMMENT
18
+ #{indent primary_key.description}
19
+ COMMENT
20
+ RUBY
21
+ end
22
+
23
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
24
+
25
+ add_migration primary_key.table.schema.name, primary_key.table.name, :add_primary_key, primary_key.name, code_comment, <<~RUBY
26
+ add_primary_key :#{primary_key.table.name}, #{column_names}, #{options_syntax}
27
+ RUBY
28
+ end
29
+
30
+ def remove_primary_key primary_key, code_comment = nil
31
+ add_migration primary_key.table.schema.name, primary_key.table.name, :remove_primary_key, primary_key.name, code_comment, <<~RUBY
32
+ remove_primary_key :#{primary_key.table.name}, :#{primary_key.name}
33
+ RUBY
34
+ end
35
+
36
+ def recreate_primary_key original_primary_key, updated_primary_key
37
+ # remove the original primary_key
38
+ removal_fragment = remove_primary_key original_primary_key, <<~CODE_COMMENT
39
+ Removing original primary key because it has changed (it is recreated below)
40
+ Changes:
41
+ #{indent original_primary_key.differences_descriptions(updated_primary_key).join("\n")}
42
+ CODE_COMMENT
43
+
44
+ # recrete the primary_key with the new options
45
+ recreation_fragment = add_primary_key updated_primary_key, <<~CODE_COMMENT
46
+ Recreating this primary key
47
+ CODE_COMMENT
48
+
49
+ # return the new fragments (the main reason to return them here is for the specs)
50
+ [removal_fragment, recreation_fragment]
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module Schema
5
+ def create_schema schema, code_comment = nil
6
+ add_migration schema.name, nil, :create_schema, schema.name, code_comment, <<~RUBY
7
+ create_schema :#{schema.name}
8
+ RUBY
9
+ end
10
+
11
+ def drop_schema schema, code_comment = nil
12
+ add_migration schema.name, nil, :drop_schema, schema.name, code_comment, <<~RUBY
13
+ drop_schema :#{schema.name}
14
+ RUBY
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ class SchemaMigrations
5
+ class Section
6
+ attr_reader :schema_name
7
+ attr_reader :table_name
8
+ attr_reader :content_type
9
+ attr_reader :fragment
10
+
11
+ def initialize schema_name, table_name, content_type, fragment
12
+ @schema_name = schema_name
13
+ @table_name = table_name
14
+ @content_type = content_type
15
+ @fragment = fragment
16
+ end
17
+
18
+ def object_name
19
+ @fragment.object_name
20
+ end
21
+
22
+ def to_s
23
+ @fragment.to_s
24
+ end
25
+
26
+ def is_comment?
27
+ content_type? :comment
28
+ end
29
+
30
+ def content_type? content_type
31
+ @content_type == content_type
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ class SchemaMigrations
5
+ class SectionNotFoundError < StandardError
6
+ end
7
+
8
+ attr_reader :current_migration_sections
9
+
10
+ def initialize
11
+ @migrations = []
12
+ @current_migration_sections = []
13
+ end
14
+
15
+ def add_fragment schema_name, table_name, content_type, fragment
16
+ @current_migration_sections << Section.new(schema_name, table_name, content_type, fragment)
17
+ end
18
+
19
+ def finalize
20
+ if @current_migration_sections.any?
21
+
22
+ contents = []
23
+ @current_migration_sections.each do |section|
24
+ contents << section.to_s
25
+ # add an empty line between sections (unless this is a comment section)
26
+ unless section.is_comment?
27
+ contents << ""
28
+ end
29
+ end
30
+
31
+ @migrations << {
32
+ name: generate_current_migration_name,
33
+ content: contents.join("\n").strip
34
+ }
35
+
36
+ @current_migration_sections = []
37
+ end
38
+ end
39
+
40
+ def to_a
41
+ @migrations
42
+ end
43
+
44
+ private
45
+
46
+ def current_migration_has_content_type? content_type
47
+ @current_migration_sections.map(&:content_type).include? content_type
48
+ end
49
+
50
+ def current_migration_section_of_content_type content_type
51
+ section = @current_migration_sections.find(&:content_type)
52
+ if section.nil?
53
+ raise SectionNotFoundError, "No section of type #{content_type} found"
54
+ end
55
+ section
56
+ end
57
+
58
+ # return true if the current migration only has the provided content types and comments
59
+ def current_migration_only_content_types? content_types
60
+ (@current_migration_sections.map(&:content_type) - content_types - [:comment]).empty?
61
+ end
62
+
63
+ def generate_current_migration_name
64
+ if current_migration_has_content_type? :create_schema
65
+ "create_#{current_migration_section_of_content_type(:create_schema).schema_name}_schema".to_sym
66
+
67
+ elsif current_migration_has_content_type? :drop_schema
68
+ "drop_#{current_migration_section_of_content_type(:drop_schema).schema_name}_schema".to_sym
69
+
70
+ elsif current_migration_has_content_type? :create_table
71
+ "create_#{current_migration_section_of_content_type(:create_table).table_name}".to_sym
72
+
73
+ elsif current_migration_has_content_type? :drop_table
74
+ "drop_#{current_migration_section_of_content_type(:drop_table).table_name}".to_sym
75
+
76
+ elsif current_migration_only_content_types? [:create_function]
77
+ "create_function_#{@current_migration_sections.find { |s| s.content_type == :create_function }&.object_name}".to_sym
78
+
79
+ elsif current_migration_only_content_types? [:create_function, :update_function, :drop_function, :set_function_comment, :remove_function_comment]
80
+ :schema_functions
81
+
82
+ elsif @current_migration_sections.first&.table_name
83
+ "changes_for_#{@current_migration_sections.first&.table_name}".to_sym
84
+
85
+ else
86
+ :changes
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end