dynamic_migrations 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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