dynamic_migrations 3.1.0 → 3.2.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 (24) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/lib/dynamic_migrations/postgres/generator/column.rb +44 -19
  4. data/lib/dynamic_migrations/postgres/generator/foreign_key_constraint.rb +35 -14
  5. data/lib/dynamic_migrations/postgres/generator/fragment.rb +37 -2
  6. data/lib/dynamic_migrations/postgres/generator/function.rb +50 -25
  7. data/lib/dynamic_migrations/postgres/generator/index.rb +34 -14
  8. data/lib/dynamic_migrations/postgres/generator/migration.rb +175 -0
  9. data/lib/dynamic_migrations/postgres/generator/migration_dependency_sorter.rb +21 -0
  10. data/lib/dynamic_migrations/postgres/generator/primary_key.rb +16 -6
  11. data/lib/dynamic_migrations/postgres/generator/schema.rb +14 -6
  12. data/lib/dynamic_migrations/postgres/generator/schema_migration.rb +17 -0
  13. data/lib/dynamic_migrations/postgres/generator/table.rb +39 -19
  14. data/lib/dynamic_migrations/postgres/generator/table_migration.rb +51 -0
  15. data/lib/dynamic_migrations/postgres/generator/trigger.rb +34 -14
  16. data/lib/dynamic_migrations/postgres/generator/unique_constraint.rb +34 -14
  17. data/lib/dynamic_migrations/postgres/generator/validation.rb +38 -18
  18. data/lib/dynamic_migrations/postgres/generator.rb +163 -294
  19. data/lib/dynamic_migrations/postgres/server/database/connection.rb +15 -0
  20. data/lib/dynamic_migrations/version.rb +1 -1
  21. data/lib/dynamic_migrations.rb +4 -2
  22. metadata +6 -4
  23. data/lib/dynamic_migrations/postgres/generator/schema_migrations/section.rb +0 -37
  24. data/lib/dynamic_migrations/postgres/generator/schema_migrations.rb +0 -92
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f112c797c02407f6c2b090fcd5dabd05861a864069f6aba51a96467be7a093f2
4
- data.tar.gz: 78bb49e74e722db75f9a6366d6034b947632a41326241f24e51ea6eba4879720
3
+ metadata.gz: '05957cccec4b1ee5138e9e74daa7714da41b99be958f7171e1c88475fe495e7b'
4
+ data.tar.gz: fd7cb71f07ded6599286dac27c274832bf97de43e56b69a47b7238aefaac2cde
5
5
  SHA512:
6
- metadata.gz: 9ed20eadb8e64389d12665938cff6702d19c2ec5d8c8e9a8b52cc0a7cf715c76596eb9a868ae0411c3f417e39425708a96ee8b890ea40b4f6403ce67dc5daa92
7
- data.tar.gz: 49aa26f15e57e2d4f18c5dc850e35946781881b63fe52182eb7d707605b9615f4c1d9c6ac1c4807782577646712207032d0a775614a4552ad6fbef7d83d68157
6
+ metadata.gz: f1729685ce54c91e0a3a8ce03fbb552887450b843f4c654678717aba41c1e52b66dff1104b9acaedd955e221267619e64405fe4088af9de54b9216446bf87984
7
+ data.tar.gz: 95668080a1d329fcaf0172e3593cfc59bab34b7edca05fedfe23d10d7b83945bd9c79943768765f505997fdd8781721569b2845d752d8f99450d251758d41eac
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.2.0](https://github.com/craigulliott/dynamic_migrations/compare/v3.1.1...v3.2.0) (2023-08-16)
4
+
5
+
6
+ ### Features
7
+
8
+ * adding a with_connection method to the database which yields to a block, and provides a connection object to that block ([fc1590a](https://github.com/craigulliott/dynamic_migrations/commit/fc1590a00be8d3f9f0ee0472a96be94b80e667e0))
9
+
10
+ ## [3.1.1](https://github.com/craigulliott/dynamic_migrations/compare/v3.1.0...v3.1.1) (2023-08-16)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * refactoring the migration generator so that it handles dependencies between migrations ([8d0f8d8](https://github.com/craigulliott/dynamic_migrations/commit/8d0f8d8cd4a22b974b6449c62ad2a2d74aeffb2e))
16
+
3
17
  ## [3.1.0](https://github.com/craigulliott/dynamic_migrations/compare/v3.0.0...v3.1.0) (2023-08-14)
4
18
 
5
19
 
@@ -28,11 +28,16 @@ module DynamicMigrations
28
28
  data_type = "\"#{data_type}\""
29
29
  end
30
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
31
+ add_fragment schema: column.table.schema,
32
+ table: column.table,
33
+ migration_method: :add_column,
34
+ object: column,
35
+ code_comment: code_comment,
36
+ migration: <<~RUBY
37
+ add_column :#{column.table.name}, :#{column.name}, :#{data_type}, #{options_syntax}, comment: <<~COMMENT
38
+ #{indent column.description}
39
+ COMMENT
40
+ RUBY
36
41
  end
37
42
 
38
43
  def change_column column, code_comment = nil
@@ -54,15 +59,25 @@ module DynamicMigrations
54
59
  data_type = ":\"#{data_type}\""
55
60
  end
56
61
 
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
62
+ add_fragment schema: column.table.schema,
63
+ table: column.table,
64
+ migration_method: :change_column,
65
+ object: column,
66
+ code_comment: code_comment,
67
+ migration: <<~RUBY
68
+ change_column :#{column.table.name}, :#{column.name}, :#{data_type}, #{options_syntax}
69
+ RUBY
60
70
  end
61
71
 
62
72
  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
73
+ add_fragment schema: column.table.schema,
74
+ table: column.table,
75
+ migration_method: :remove_column,
76
+ object: column,
77
+ code_comment: code_comment,
78
+ migration: <<~RUBY
79
+ remove_column :#{column.table.name}, :#{column.name}
80
+ RUBY
66
81
  end
67
82
 
68
83
  # add a comment to a column
@@ -73,18 +88,28 @@ module DynamicMigrations
73
88
  raise MissingDescriptionError
74
89
  end
75
90
 
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
91
+ add_fragment schema: column.table.schema,
92
+ table: column.table,
93
+ migration_method: :set_column_comment,
94
+ object: column,
95
+ code_comment: code_comment,
96
+ migration: <<~RUBY
97
+ set_column_comment :#{column.table.name}, :#{column.name}, <<~COMMENT
98
+ #{indent description}
99
+ COMMENT
100
+ RUBY
81
101
  end
82
102
 
83
103
  # remove the comment from a column
84
104
  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
105
+ add_fragment schema: column.table.schema,
106
+ table: column.table,
107
+ migration_method: :remove_column_comment,
108
+ object: column,
109
+ code_comment: code_comment,
110
+ migration: <<~RUBY
111
+ remove_column_comment :#{column.table.name}, :#{column.name}
112
+ RUBY
88
113
  end
89
114
  end
90
115
  end
@@ -29,15 +29,26 @@ module DynamicMigrations
29
29
 
30
30
  options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
31
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
32
+ add_fragment schema: foreign_key_constraint.table.schema,
33
+ table: foreign_key_constraint.table,
34
+ migration_method: :add_foreign_key,
35
+ object: foreign_key_constraint,
36
+ code_comment: code_comment,
37
+ dependent_table: foreign_key_constraint.foreign_table,
38
+ migration: <<~RUBY
39
+ add_foreign_key :#{foreign_key_constraint.table.name}, #{column_names}, :#{foreign_key_constraint.foreign_table.name}, #{foreign_column_names}, #{options_syntax}
40
+ RUBY
35
41
  end
36
42
 
37
43
  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
44
+ add_fragment schema: foreign_key_constraint.table.schema,
45
+ table: foreign_key_constraint.table,
46
+ migration_method: :remove_foreign_key,
47
+ object: foreign_key_constraint,
48
+ code_comment: code_comment,
49
+ migration: <<~RUBY
50
+ remove_foreign_key :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}
51
+ RUBY
41
52
  end
42
53
 
43
54
  def recreate_foreign_key_constraint original_foreign_key_constraint, updated_foreign_key_constraint
@@ -65,18 +76,28 @@ module DynamicMigrations
65
76
  raise MissingDescriptionError
66
77
  end
67
78
 
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
79
+ add_fragment schema: foreign_key_constraint.table.schema,
80
+ table: foreign_key_constraint.table,
81
+ migration_method: :set_foreign_key_constraint_comment,
82
+ object: foreign_key_constraint,
83
+ code_comment: code_comment,
84
+ migration: <<~RUBY
85
+ set_foreign_key_comment :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}, <<~COMMENT
86
+ #{indent description}
87
+ COMMENT
88
+ RUBY
73
89
  end
74
90
 
75
91
  # remove the comment from a foreign_key_constraint
76
92
  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
93
+ add_fragment schema: foreign_key_constraint.table.schema,
94
+ table: foreign_key_constraint.table,
95
+ migration_method: :remove_foreign_key_constraint_comment,
96
+ object: foreign_key_constraint,
97
+ code_comment: code_comment,
98
+ migration: <<~RUBY
99
+ remove_foreign_key_comment :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}
100
+ RUBY
80
101
  end
81
102
  end
82
103
  end
@@ -2,15 +2,25 @@ module DynamicMigrations
2
2
  module Postgres
3
3
  class Generator
4
4
  class Fragment
5
+ attr_reader :schema_name
6
+ attr_reader :table_name
7
+ attr_reader :migration_method
5
8
  attr_reader :object_name
6
- attr_reader :code_comment
9
+ attr_reader :dependency_schema_name
10
+ attr_reader :dependency_table_name
7
11
 
8
- def initialize object_name, code_comment, content
12
+ def initialize schema_name, table_name, migration_method, object_name, code_comment, content
13
+ @schema_name = schema_name
14
+ @table_name = table_name
15
+ @migration_method = migration_method
9
16
  @object_name = object_name
10
17
  @code_comment = code_comment
11
18
  @content = content
12
19
  end
13
20
 
21
+ # Returns a string representation of the fragment for use in the final
22
+ # migration. This final string is a combination of the code_comment (if present)
23
+ # and the content of the fragment.
14
24
  def to_s
15
25
  strings = []
16
26
  comment = @code_comment
@@ -21,9 +31,34 @@ module DynamicMigrations
21
31
  strings.join("\n").strip
22
32
  end
23
33
 
34
+ # Returns true if the fragment has a code comment, otherwise false.
24
35
  def has_code_comment?
25
36
  !@code_comment.nil?
26
37
  end
38
+
39
+ # If a table dependency has been set, then returns a hash with the schema_name
40
+ # and table_name, otherwise returns nil.
41
+ def dependency
42
+ if dependency_schema_name && dependency_table_name
43
+ {
44
+ schema_name: dependency_schema_name,
45
+ table_name: dependency_table_name
46
+ }
47
+ end
48
+ end
49
+
50
+ # returns true if the fragment has a table dependency, and the dependency matches
51
+ # the provided schema_name and table_name, otherwise returns false.
52
+ def is_dependent_on? schema_name, table_name
53
+ dependency_schema_name && schema_name == dependency_schema_name && table_name == dependency_table_name || false
54
+ end
55
+
56
+ # Set the table table dependency for this fragment. Takes a schema name and
57
+ # table name
58
+ def set_dependent_table schema_name, table_name
59
+ @dependency_schema_name = schema_name
60
+ @dependency_table_name = table_name
61
+ end
27
62
  end
28
63
  end
29
64
  end
@@ -25,13 +25,18 @@ module DynamicMigrations
25
25
  fn_sql << ";"
26
26
  end
27
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
28
+ add_fragment schema: function.schema,
29
+ table: function.triggers.first&.table,
30
+ migration_method: :create_function,
31
+ object: function,
32
+ code_comment: code_comment,
33
+ migration: comment_sql + <<~RUBY
34
+ create_function :#{function.name}#{optional_options_syntax} do
35
+ <<~SQL
36
+ #{indent fn_sql}
37
+ SQL
38
+ end
39
+ RUBY
35
40
  end
36
41
 
37
42
  def update_function function, code_comment = nil
@@ -41,35 +46,55 @@ module DynamicMigrations
41
46
  fn_sql << ";"
42
47
  end
43
48
 
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
49
+ add_fragment schema: function.schema,
50
+ table: function.triggers.first&.table,
51
+ migration_method: :update_function,
52
+ object: function,
53
+ code_comment: code_comment,
54
+ migration: <<~RUBY
55
+ update_function :#{function.name} do
56
+ <<~SQL
57
+ #{indent fn_sql}
58
+ SQL
59
+ end
60
+ RUBY
51
61
  end
52
62
 
53
63
  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
64
+ add_fragment schema: function.schema,
65
+ table: function.triggers.first&.table,
66
+ migration_method: :drop_function,
67
+ object: function,
68
+ code_comment: code_comment,
69
+ migration: <<~RUBY
70
+ drop_function :#{function.name}
71
+ RUBY
57
72
  end
58
73
 
59
74
  # add a comment to a function
60
75
  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
76
+ add_fragment schema: function.schema,
77
+ table: function.triggers.first&.table,
78
+ migration_method: :set_function_comment,
79
+ object: function,
80
+ code_comment: code_comment,
81
+ migration: <<~RUBY
82
+ set_function_comment :#{function.name}, <<~COMMENT
83
+ #{indent function.description || ""}
84
+ COMMENT
85
+ RUBY
66
86
  end
67
87
 
68
88
  # remove the comment from a function
69
89
  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
90
+ add_fragment schema: function.schema,
91
+ table: function.triggers.first&.table,
92
+ migration_method: :remove_function_comment,
93
+ object: function,
94
+ code_comment: code_comment,
95
+ migration: <<~RUBY
96
+ remove_function_comment :#{function.name}
97
+ RUBY
73
98
  end
74
99
  end
75
100
  end
@@ -46,15 +46,25 @@ module DynamicMigrations
46
46
 
47
47
  options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
48
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
49
+ add_fragment schema: index.table.schema,
50
+ table: index.table,
51
+ migration_method: :add_index,
52
+ object: index,
53
+ code_comment: code_comment,
54
+ migration: where_sql + <<~RUBY
55
+ add_index :#{index.table.name}, #{column_names}, #{options_syntax}
56
+ RUBY
52
57
  end
53
58
 
54
59
  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
60
+ add_fragment schema: index.table.schema,
61
+ table: index.table,
62
+ migration_method: :remove_index,
63
+ object: index,
64
+ code_comment: code_comment,
65
+ migration: <<~RUBY
66
+ remove_index :#{index.table.name}, :#{index.name}
67
+ RUBY
58
68
  end
59
69
 
60
70
  def recreate_index original_index, updated_index
@@ -82,18 +92,28 @@ module DynamicMigrations
82
92
  raise MissingDescriptionError
83
93
  end
84
94
 
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
95
+ add_fragment schema: index.table.schema,
96
+ table: index.table,
97
+ migration_method: :set_index_comment,
98
+ object: index,
99
+ code_comment: code_comment,
100
+ migration: <<~RUBY
101
+ set_index_comment :#{index.table.name}, :#{index.name}, <<~COMMENT
102
+ #{indent description}
103
+ COMMENT
104
+ RUBY
90
105
  end
91
106
 
92
107
  # remove the comment from a index
93
108
  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
109
+ add_fragment schema: index.table.schema,
110
+ table: index.table,
111
+ migration_method: :remove_index_comment,
112
+ object: index,
113
+ code_comment: code_comment,
114
+ migration: <<~RUBY
115
+ remove_index_comment :#{index.table.name}, :#{index.name}
116
+ RUBY
97
117
  end
98
118
  end
99
119
  end
@@ -0,0 +1,175 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ class Migration
5
+ class UnexpectedSchemaError < StandardError
6
+ end
7
+
8
+ class SectionNotFoundError < StandardError
9
+ end
10
+
11
+ class UnexpectedMigrationMethodNameError < StandardError
12
+ end
13
+
14
+ class DuplicateStructureTemplateError < StandardError
15
+ end
16
+
17
+ class NoFragmentsError < StandardError
18
+ end
19
+
20
+ # Defines a new section in the migration file, this is used to group
21
+ # migration fragments of the provided method names together under the
22
+ # provided header
23
+ def self.add_structure_template method_names, header
24
+ @structure_templates ||= []
25
+
26
+ if (@structure_templates.map { |s| s[:methods] }.flatten & method_names).any?
27
+ raise DuplicateStructureTemplateError, "Duplicate structure template for methods `#{method_names}`"
28
+ end
29
+
30
+ @structure_templates << {
31
+ methods: method_names,
32
+ header_comment: <<~COMMENT.strip
33
+ #
34
+ # #{header.strip}
35
+ #
36
+ COMMENT
37
+ }
38
+ end
39
+
40
+ # return the list of structure templates for use in this migration
41
+ def self.structure_templates
42
+ @structure_templates || []
43
+ end
44
+
45
+ # return the list of structure templates for use in this migration
46
+ def self.clear_structure_templates
47
+ @structure_templates = []
48
+ end
49
+
50
+ attr_reader :schema_name
51
+ attr_reader :fragments
52
+
53
+ def initialize schema_name
54
+ @schema_name = schema_name
55
+ @fragments = []
56
+ end
57
+
58
+ # Add a migration fragment to this migration, if the migration is not
59
+ # configured (via a structure template) to handle the method_name of the
60
+ # fragment, then am error is raised. An error will also be raised if the
61
+ # migration belongs to a different schema than the provided fragment.
62
+ def add_fragment fragment
63
+ raise UnexpectedSchemaError unless @schema_name == fragment.schema_name
64
+
65
+ unless supported_migration_method? fragment.migration_method
66
+ raise UnexpectedMigrationMethodNameError, "Expected method to be a valid migrator method, got `#{fragment.migration_method}`"
67
+ end
68
+
69
+ @fragments << fragment
70
+
71
+ fragment
72
+ end
73
+
74
+ # Return an array of table dependencies for this migration, this array comes from
75
+ # combining any table dependencies from each fragment.
76
+ # Will raise an error if no fragments have been provided.
77
+ def dependencies
78
+ raise NoFragmentsError if fragments.empty?
79
+ @fragments.map(&:dependency).compact
80
+ end
81
+
82
+ # removes and returns any fragments which have a dependency on the table with the
83
+ # provided schema_name and table_name, this is used for extracting fragments which
84
+ # cause circular dependencies so they can be placed into their own migrations
85
+ def extract_fragments_with_dependency schema_name, table_name
86
+ results = @fragments.filter { |f| f.is_dependent_on? schema_name, table_name }
87
+ # remove any of these from the internal array of fragments
88
+ @fragments.filter! { |f| !f.is_dependent_on?(schema_name, table_name) }
89
+ # return the results
90
+ results
91
+ end
92
+
93
+ # Combine the fragments, and build a string representation of the migration
94
+ # using the structure templates defined in this class.
95
+ # Will raise an error if no fragments have been provided.
96
+ def content
97
+ raise NoFragmentsError if fragments.empty?
98
+ sections = []
99
+ self.class.structure_templates.each do |section|
100
+ # add the header comment if we have a migration which matches one of the
101
+ # methods in this section
102
+ if (section[:methods] & @fragments.map(&:migration_method)).any?
103
+ sections << section[:header_comment].strip
104
+ end
105
+
106
+ # iterate through this sections methods in order and look
107
+ # for any that match the migrations we have
108
+ section[:methods].each do |migration_method|
109
+ # if we have any migration fragments for this method then add them
110
+ @fragments.filter { |f| f.migration_method == migration_method }.each do |fragment|
111
+ sections << fragment.to_s
112
+ sections << ""
113
+ end
114
+ end
115
+ end
116
+ sections.join("\n").strip
117
+ end
118
+
119
+ # Using the migration fragments, generate a friendly/descriptive name for the migration.
120
+ # Will raise an error if no fragments have been provided.
121
+ def name
122
+ raise NoFragmentsError if fragments.empty?
123
+
124
+ if fragments_for_method? :create_schema
125
+ "create_#{first_fragment_using_migration_method(:create_schema).schema_name}_schema".to_sym
126
+
127
+ elsif fragments_for_method? :drop_schema
128
+ "drop_#{first_fragment_using_migration_method(:drop_schema).schema_name}_schema".to_sym
129
+
130
+ elsif fragments_for_method? :create_table
131
+ "create_#{first_fragment_using_migration_method(:create_table).table_name}".to_sym
132
+
133
+ elsif fragments_for_method? :drop_table
134
+ "drop_#{first_fragment_using_migration_method(:drop_table).table_name}".to_sym
135
+
136
+ elsif all_fragments_for_method? [:create_function]
137
+ "create_function_#{@fragments.find { |s| s.migration_method == :create_function }&.object_name}".to_sym
138
+
139
+ elsif all_fragments_for_method? [:create_function, :update_function, :drop_function, :set_function_comment, :remove_function_comment]
140
+ :schema_functions
141
+
142
+ elsif @fragments.first&.table_name
143
+ "changes_for_#{@fragments.first&.table_name}".to_sym
144
+
145
+ else
146
+ :changes
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ def supported_migration_method? method_name
153
+ self.class.structure_templates.map { |s| s[:methods] }.flatten.include? method_name
154
+ end
155
+
156
+ def fragments_for_method? migration_method
157
+ @fragments.map(&:migration_method).include? migration_method
158
+ end
159
+
160
+ def first_fragment_using_migration_method migration_method
161
+ fragment = @fragments.find(&:migration_method)
162
+ if fragment.nil?
163
+ raise SectionNotFoundError, "No fragment of type #{migration_method} found"
164
+ end
165
+ fragment
166
+ end
167
+
168
+ # return true if the current migration only has the provided content types and comments
169
+ def all_fragments_for_method? migration_methods
170
+ (@fragments.map(&:migration_method) - migration_methods - [:comment]).empty?
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,21 @@
1
+ # TSort is a module included in the Ruby standard library for
2
+ # executing topological sorts. We use it here to sort the migration
3
+ # fragments so that they are executed in the correct order (i.e. tables
4
+ # which have foreign keys are created after the tables they point to).
5
+ require "tsort"
6
+
7
+ module DynamicMigrations
8
+ module Postgres
9
+ class Generator
10
+ class MigrationDependencySorter < Hash
11
+ include TSort
12
+
13
+ alias_method :tsort_each_node, :each_key
14
+
15
+ def tsort_each_child(node, &block)
16
+ fetch(node).each(&block)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -22,15 +22,25 @@ module DynamicMigrations
22
22
 
23
23
  options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
24
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
25
+ add_fragment schema: primary_key.table.schema,
26
+ table: primary_key.table,
27
+ migration_method: :add_primary_key,
28
+ object: primary_key,
29
+ code_comment: code_comment,
30
+ migration: <<~RUBY
31
+ add_primary_key :#{primary_key.table.name}, #{column_names}, #{options_syntax}
32
+ RUBY
28
33
  end
29
34
 
30
35
  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
36
+ add_fragment schema: primary_key.table.schema,
37
+ table: primary_key.table,
38
+ migration_method: :remove_primary_key,
39
+ object: primary_key,
40
+ code_comment: code_comment,
41
+ migration: <<~RUBY
42
+ remove_primary_key :#{primary_key.table.name}, :#{primary_key.name}
43
+ RUBY
34
44
  end
35
45
 
36
46
  def recreate_primary_key original_primary_key, updated_primary_key