dynamic_migrations 3.0.0 → 3.1.1

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 +21 -6
  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/differences.rb +6 -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: 389dc33f29bc39c47961125ad36f5faa377d8609d6d64c2f008517c81bbeb44c
4
- data.tar.gz: 27bb1d8d55727f8467b52b7240373bfaf397262d88adfa07db2a04a5d3b52e65
3
+ metadata.gz: 971a68fa07394767b18c8781df8be28eada7a1024256698f63bc4c0b0c06a560
4
+ data.tar.gz: 266f91ca87e1da40a0fbd13a9cde183e167c2950f1d0718169fdb8e4ff4eec52
5
5
  SHA512:
6
- metadata.gz: bf62099155ca8274632d52fbe7328e66eed962a73cbafd6c53cd056ca8fcea00851ddec52263f47d82ea8486756ea89bf79d03d8b2d2fa489399c7f7e60fb3c0
7
- data.tar.gz: 0ea7b26061538ae65d1b1c9cddf91dd8afa3a8c2ac3ff41416926235ccb5e0b2ad6cad304129b038d39939cab32492d00c289a85331cc7724a24678b0e202821
6
+ metadata.gz: f4f5ca51ab18b44f7a832d9442c7412e725c6b4d688fd481d45752168a6fb6665b15cf288934399d0eb7ef64e8a78a44730fa138cb0f1ff0e1435c135a8b627e
7
+ data.tar.gz: 223b0432f2198a98f33dfeba24f156938b47d286908a62571f93b7acfebeef6fc5594f0eac8c47805e01c1038c25af6af68321e0e829d4f673b5a5292587eede
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.1.1](https://github.com/craigulliott/dynamic_migrations/compare/v3.1.0...v3.1.1) (2023-08-16)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * refactoring the migration generator so that it handles dependencies between migrations ([8d0f8d8](https://github.com/craigulliott/dynamic_migrations/commit/8d0f8d8cd4a22b974b6449c62ad2a2d74aeffb2e))
9
+
10
+ ## [3.1.0](https://github.com/craigulliott/dynamic_migrations/compare/v3.0.0...v3.1.0) (2023-08-14)
11
+
12
+
13
+ ### Features
14
+
15
+ * adding a convenience method `to_migrations` on the differences class ([efe94be](https://github.com/craigulliott/dynamic_migrations/commit/efe94be9c620fc44d1cdba54f6cd365292ae0f34))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * updating to_migrations method signature in differences class ([e4e502b](https://github.com/craigulliott/dynamic_migrations/commit/e4e502b7ec44add3d2b6727869a4ad90bc7531b7))
21
+
3
22
  ## [3.0.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.2.0...v3.0.0) (2023-08-11)
4
23
 
5
24
 
@@ -13,8 +32,6 @@
13
32
  * 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
33
  * now generating migrations from all database structure object types ([871ab04](https://github.com/craigulliott/dynamic_migrations/commit/871ab048efb6247bc7fadb6e49bb15f6933c5586))
15
34
  * 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
35
 
19
36
  ## [2.2.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.1.0...v2.2.0) (2023-07-31)
20
37
 
@@ -35,11 +52,11 @@
35
52
 
36
53
  ### ⚠ BREAKING CHANGES
37
54
 
38
- * changing all name related methods from %object%_name to just name (i.e. `table.table_name` is now just `table.name`)
55
+ * changing all name related methods from `%object%_name` to just `name` (i.e. `table.table_name` is now just `table.name`)
39
56
 
40
57
  ### Features
41
58
 
42
- * changing all name related methods from %object%_name to just name (i.e. `table.table_name` is now just `table.name`) ([77f18ae](https://github.com/craigulliott/dynamic_migrations/commit/77f18ae168c2449fa437fa7692ff9339931f9076))
59
+ * changing all name related methods from `%object%_name` to just `name` (i.e. `table.table_name` is now just `table.name`) ([77f18ae](https://github.com/craigulliott/dynamic_migrations/commit/77f18ae168c2449fa437fa7692ff9339931f9076))
43
60
 
44
61
  ## [1.1.1](https://github.com/craigulliott/dynamic_migrations/compare/v1.1.0...v1.1.1) (2023-07-17)
45
62
 
@@ -47,8 +64,6 @@
47
64
  ### Bug Fixes
48
65
 
49
66
  * validating that new databases don't already exist and removing a debug print statement ([73bed2d](https://github.com/craigulliott/dynamic_migrations/commit/73bed2d6ab928c7a8f53b2aa17a188a77ed369e9))
50
- * validating that new databases don't already exist and removing a debug print statement ([73bed2d](https://github.com/craigulliott/dynamic_migrations/commit/73bed2d6ab928c7a8f53b2aa17a188a77ed369e9))
51
- * validating that new databases don't already exist and removing a debug print statement ([f4cfb1e](https://github.com/craigulliott/dynamic_migrations/commit/f4cfb1e81252ed967ad1a6d864d495c182ca908f))
52
67
 
53
68
  ## [1.1.0](https://github.com/craigulliott/dynamic_migrations/compare/v1.0.0...v1.1.0) (2023-07-10)
54
69
 
@@ -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