pg_trunk 0.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 (196) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +87 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +92 -0
  6. data/.yardopts +4 -0
  7. data/CHANGELOG.md +31 -0
  8. data/CONTRIBUTING.md +17 -0
  9. data/Gemfile +22 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +141 -0
  12. data/Rakefile +16 -0
  13. data/bin/console +8 -0
  14. data/bin/rake +19 -0
  15. data/bin/rspec +19 -0
  16. data/bin/setup +8 -0
  17. data/bin/yard +19 -0
  18. data/lib/pg_trunk/core/adapters/postgres.rb +80 -0
  19. data/lib/pg_trunk/core/dependencies_resolver.rb +101 -0
  20. data/lib/pg_trunk/core/generators.rb +140 -0
  21. data/lib/pg_trunk/core/operation/attributes.rb +78 -0
  22. data/lib/pg_trunk/core/operation/callbacks.rb +40 -0
  23. data/lib/pg_trunk/core/operation/generators.rb +51 -0
  24. data/lib/pg_trunk/core/operation/inversion.rb +70 -0
  25. data/lib/pg_trunk/core/operation/registration.rb +55 -0
  26. data/lib/pg_trunk/core/operation/ruby_builder.rb +112 -0
  27. data/lib/pg_trunk/core/operation/ruby_helpers.rb +99 -0
  28. data/lib/pg_trunk/core/operation/sql_helpers.rb +44 -0
  29. data/lib/pg_trunk/core/operation/validations.rb +21 -0
  30. data/lib/pg_trunk/core/operation.rb +78 -0
  31. data/lib/pg_trunk/core/qualified_name.rb +165 -0
  32. data/lib/pg_trunk/core/railtie/command_recorder.rb +30 -0
  33. data/lib/pg_trunk/core/railtie/custom_types.rb +37 -0
  34. data/lib/pg_trunk/core/railtie/migration.rb +50 -0
  35. data/lib/pg_trunk/core/railtie/migrator.rb +22 -0
  36. data/lib/pg_trunk/core/railtie/schema_dumper.rb +75 -0
  37. data/lib/pg_trunk/core/railtie/schema_migration.rb +22 -0
  38. data/lib/pg_trunk/core/railtie/statements.rb +21 -0
  39. data/lib/pg_trunk/core/railtie.rb +35 -0
  40. data/lib/pg_trunk/core/registry.rb +159 -0
  41. data/lib/pg_trunk/core/serializers/array_of_hashes_serializer.rb +28 -0
  42. data/lib/pg_trunk/core/serializers/array_of_strings_serializer.rb +29 -0
  43. data/lib/pg_trunk/core/serializers/array_of_symbols_serializer.rb +28 -0
  44. data/lib/pg_trunk/core/serializers/array_serializer.rb +22 -0
  45. data/lib/pg_trunk/core/serializers/lowercase_string_serializer.rb +21 -0
  46. data/lib/pg_trunk/core/serializers/multiline_text_serializer.rb +21 -0
  47. data/lib/pg_trunk/core/serializers/qualified_name_serializer.rb +27 -0
  48. data/lib/pg_trunk/core/serializers/symbol_serializer.rb +22 -0
  49. data/lib/pg_trunk/core/serializers.rb +16 -0
  50. data/lib/pg_trunk/core/validators/all_items_valid_validator.rb +15 -0
  51. data/lib/pg_trunk/core/validators/difference_validator.rb +19 -0
  52. data/lib/pg_trunk/core/validators.rb +10 -0
  53. data/lib/pg_trunk/core.rb +21 -0
  54. data/lib/pg_trunk/generators.rb +7 -0
  55. data/lib/pg_trunk/operations/check_constraints/add_check_constraint.rb +109 -0
  56. data/lib/pg_trunk/operations/check_constraints/base.rb +69 -0
  57. data/lib/pg_trunk/operations/check_constraints/drop_check_constraint.rb +60 -0
  58. data/lib/pg_trunk/operations/check_constraints/rename_check_constraint.rb +54 -0
  59. data/lib/pg_trunk/operations/check_constraints/validate_check_constraint.rb +39 -0
  60. data/lib/pg_trunk/operations/check_constraints.rb +14 -0
  61. data/lib/pg_trunk/operations/composite_types/base.rb +61 -0
  62. data/lib/pg_trunk/operations/composite_types/change_composite_type.rb +136 -0
  63. data/lib/pg_trunk/operations/composite_types/column.rb +118 -0
  64. data/lib/pg_trunk/operations/composite_types/create_composite_type.rb +99 -0
  65. data/lib/pg_trunk/operations/composite_types/drop_composite_type.rb +67 -0
  66. data/lib/pg_trunk/operations/composite_types/rename_composite_type.rb +44 -0
  67. data/lib/pg_trunk/operations/composite_types.rb +15 -0
  68. data/lib/pg_trunk/operations/domains/base.rb +46 -0
  69. data/lib/pg_trunk/operations/domains/change_domain.rb +140 -0
  70. data/lib/pg_trunk/operations/domains/constraint.rb +93 -0
  71. data/lib/pg_trunk/operations/domains/create_domain.rb +124 -0
  72. data/lib/pg_trunk/operations/domains/drop_domain.rb +65 -0
  73. data/lib/pg_trunk/operations/domains/rename_domain.rb +44 -0
  74. data/lib/pg_trunk/operations/domains.rb +15 -0
  75. data/lib/pg_trunk/operations/enums/base.rb +47 -0
  76. data/lib/pg_trunk/operations/enums/change.rb +55 -0
  77. data/lib/pg_trunk/operations/enums/change_enum.rb +119 -0
  78. data/lib/pg_trunk/operations/enums/create_enum.rb +83 -0
  79. data/lib/pg_trunk/operations/enums/drop_enum.rb +63 -0
  80. data/lib/pg_trunk/operations/enums/rename_enum.rb +44 -0
  81. data/lib/pg_trunk/operations/enums.rb +15 -0
  82. data/lib/pg_trunk/operations/foreign_keys/add_foreign_key.rb +174 -0
  83. data/lib/pg_trunk/operations/foreign_keys/base.rb +155 -0
  84. data/lib/pg_trunk/operations/foreign_keys/drop_foreign_key.rb +76 -0
  85. data/lib/pg_trunk/operations/foreign_keys/rename_foreign_key.rb +63 -0
  86. data/lib/pg_trunk/operations/foreign_keys.rb +16 -0
  87. data/lib/pg_trunk/operations/functions/base.rb +54 -0
  88. data/lib/pg_trunk/operations/functions/change_function.rb +108 -0
  89. data/lib/pg_trunk/operations/functions/create_function.rb +198 -0
  90. data/lib/pg_trunk/operations/functions/drop_function.rb +88 -0
  91. data/lib/pg_trunk/operations/functions/rename_function.rb +57 -0
  92. data/lib/pg_trunk/operations/functions.rb +14 -0
  93. data/lib/pg_trunk/operations/indexes/add_index.rb +68 -0
  94. data/lib/pg_trunk/operations/indexes.rb +10 -0
  95. data/lib/pg_trunk/operations/materialized_views/base.rb +79 -0
  96. data/lib/pg_trunk/operations/materialized_views/change_materialized_view.rb +139 -0
  97. data/lib/pg_trunk/operations/materialized_views/column.rb +94 -0
  98. data/lib/pg_trunk/operations/materialized_views/create_materialized_view.rb +170 -0
  99. data/lib/pg_trunk/operations/materialized_views/drop_materialized_view.rb +70 -0
  100. data/lib/pg_trunk/operations/materialized_views/refresh_materialized_view.rb +48 -0
  101. data/lib/pg_trunk/operations/materialized_views/rename_materialized_view.rb +61 -0
  102. data/lib/pg_trunk/operations/materialized_views.rb +17 -0
  103. data/lib/pg_trunk/operations/procedures/base.rb +42 -0
  104. data/lib/pg_trunk/operations/procedures/change_procedure.rb +107 -0
  105. data/lib/pg_trunk/operations/procedures/create_procedure.rb +146 -0
  106. data/lib/pg_trunk/operations/procedures/drop_procedure.rb +66 -0
  107. data/lib/pg_trunk/operations/procedures/rename_procedure.rb +57 -0
  108. data/lib/pg_trunk/operations/procedures.rb +14 -0
  109. data/lib/pg_trunk/operations/statistics/base.rb +94 -0
  110. data/lib/pg_trunk/operations/statistics/create_statistics.rb +181 -0
  111. data/lib/pg_trunk/operations/statistics/drop_statistics.rb +75 -0
  112. data/lib/pg_trunk/operations/statistics/rename_statistics.rb +48 -0
  113. data/lib/pg_trunk/operations/statistics.rb +13 -0
  114. data/lib/pg_trunk/operations/tables/create_table.rb +75 -0
  115. data/lib/pg_trunk/operations/tables.rb +10 -0
  116. data/lib/pg_trunk/operations/triggers/base.rb +119 -0
  117. data/lib/pg_trunk/operations/triggers/change_trigger.rb +82 -0
  118. data/lib/pg_trunk/operations/triggers/create_trigger.rb +208 -0
  119. data/lib/pg_trunk/operations/triggers/drop_trigger.rb +66 -0
  120. data/lib/pg_trunk/operations/triggers/rename_trigger.rb +71 -0
  121. data/lib/pg_trunk/operations/triggers.rb +14 -0
  122. data/lib/pg_trunk/operations/views/base.rb +38 -0
  123. data/lib/pg_trunk/operations/views/change_view.rb +90 -0
  124. data/lib/pg_trunk/operations/views/create_view.rb +115 -0
  125. data/lib/pg_trunk/operations/views/drop_view.rb +69 -0
  126. data/lib/pg_trunk/operations/views/rename_view.rb +58 -0
  127. data/lib/pg_trunk/operations/views.rb +14 -0
  128. data/lib/pg_trunk/operations.rb +23 -0
  129. data/lib/pg_trunk/version.rb +6 -0
  130. data/lib/pg_trunk.rb +27 -0
  131. data/pg_trunk.gemspec +34 -0
  132. data/spec/dummy/.gitignore +16 -0
  133. data/spec/dummy/Rakefile +15 -0
  134. data/spec/dummy/bin/bundle +6 -0
  135. data/spec/dummy/bin/rails +6 -0
  136. data/spec/dummy/bin/rake +6 -0
  137. data/spec/dummy/config/application.rb +18 -0
  138. data/spec/dummy/config/boot.rb +7 -0
  139. data/spec/dummy/config/database.yml +14 -0
  140. data/spec/dummy/config/environment.rb +7 -0
  141. data/spec/dummy/config.ru +6 -0
  142. data/spec/dummy/db/materialized_views/admin_users_v01.sql +1 -0
  143. data/spec/dummy/db/migrate/.keep +0 -0
  144. data/spec/dummy/db/schema.rb +18 -0
  145. data/spec/dummy/db/views/admin_users_v01.sql +1 -0
  146. data/spec/dummy/db/views/admin_users_v02.sql +1 -0
  147. data/spec/operations/check_constraints/add_check_constraint_spec.rb +85 -0
  148. data/spec/operations/check_constraints/drop_check_constraint_spec.rb +111 -0
  149. data/spec/operations/check_constraints/rename_check_constraint_spec.rb +90 -0
  150. data/spec/operations/composite_types/change_composite_type_spec.rb +257 -0
  151. data/spec/operations/composite_types/create_composite_type_spec.rb +55 -0
  152. data/spec/operations/composite_types/drop_composite_type_spec.rb +109 -0
  153. data/spec/operations/composite_types/rename_composite_type_spec.rb +74 -0
  154. data/spec/operations/dependency_resolver_spec.rb +177 -0
  155. data/spec/operations/domains/change_domain_spec.rb +287 -0
  156. data/spec/operations/domains/create_domain_spec.rb +69 -0
  157. data/spec/operations/domains/drop_domain_spec.rb +119 -0
  158. data/spec/operations/domains/rename_domain_spec.rb +70 -0
  159. data/spec/operations/enums/change_enum_spec.rb +157 -0
  160. data/spec/operations/enums/create_enum_spec.rb +40 -0
  161. data/spec/operations/enums/drop_enum_spec.rb +120 -0
  162. data/spec/operations/enums/rename_enum_spec.rb +72 -0
  163. data/spec/operations/foreign_keys/add_foreign_key_spec.rb +208 -0
  164. data/spec/operations/foreign_keys/drop_foreign_key_spec.rb +167 -0
  165. data/spec/operations/foreign_keys/rename_foreign_key_spec.rb +101 -0
  166. data/spec/operations/functions/change_function_spec.rb +166 -0
  167. data/spec/operations/functions/create_function_spec.rb +192 -0
  168. data/spec/operations/functions/drop_function_spec.rb +182 -0
  169. data/spec/operations/functions/rename_function_spec.rb +101 -0
  170. data/spec/operations/indexes/add_index_spec.rb +94 -0
  171. data/spec/operations/materialized_views/change_materialized_view_spec.rb +190 -0
  172. data/spec/operations/materialized_views/create_materialized_view_spec.rb +144 -0
  173. data/spec/operations/materialized_views/drop_materialized_view_spec.rb +145 -0
  174. data/spec/operations/materialized_views/refresh_materialized_view_spec.rb +79 -0
  175. data/spec/operations/materialized_views/rename_materialized_view_spec.rb +88 -0
  176. data/spec/operations/procedures/change_procedure_spec.rb +175 -0
  177. data/spec/operations/procedures/create_procedure_spec.rb +151 -0
  178. data/spec/operations/procedures/drop_procedure_spec.rb +159 -0
  179. data/spec/operations/procedures/rename_procedure_spec.rb +107 -0
  180. data/spec/operations/statistics/create_statistics_spec.rb +230 -0
  181. data/spec/operations/statistics/drop_statistics_spec.rb +106 -0
  182. data/spec/operations/statistics/rename_statistics_spec.rb +129 -0
  183. data/spec/operations/tables/create_table_spec.rb +53 -0
  184. data/spec/operations/tables/rename_table_spec.rb +37 -0
  185. data/spec/operations/triggers/change_trigger_spec.rb +195 -0
  186. data/spec/operations/triggers/create_trigger_spec.rb +104 -0
  187. data/spec/operations/triggers/drop_trigger_spec.rb +124 -0
  188. data/spec/operations/triggers/rename_trigger_spec.rb +160 -0
  189. data/spec/operations/views/change_view_spec.rb +144 -0
  190. data/spec/operations/views/create_view_spec.rb +134 -0
  191. data/spec/operations/views/drop_view_spec.rb +146 -0
  192. data/spec/operations/views/rename_view_spec.rb +85 -0
  193. data/spec/pg_trunk/dependencies_resolver_spec.rb +43 -0
  194. data/spec/spec_helper.rb +28 -0
  195. data/spec/support/migrations_helper.rb +376 -0
  196. metadata +348 -0
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#create_enum" do
4
+ before_all { run_migration "create_schema :finances" }
5
+
6
+ context "with a full definition" do
7
+ let(:migration) do
8
+ <<~RUBY
9
+ create_enum "finances.currency" do |e|
10
+ e.values "EUR", "USD"
11
+ e.comment "Supported currency values"
12
+ end
13
+ RUBY
14
+ end
15
+ let(:query) { "SELECT 'EUR'::finances.currency;" }
16
+
17
+ its(:execution) { is_expected.to enable_sql_request(query) }
18
+ its(:execution) { is_expected.to insert(migration).into_schema }
19
+ its(:inversion) { is_expected.to disable_sql_request(query) }
20
+ its(:inversion) { is_expected.not_to change_schema }
21
+ end
22
+
23
+ context "without values" do
24
+ let(:migration) { "create_enum :currency" }
25
+
26
+ it { is_expected.to fail_validation.because(/values can't be blank/i) }
27
+ end
28
+
29
+ context "without name" do
30
+ let(:migration) do
31
+ <<~RUBY
32
+ create_enum do |e|
33
+ e.values "EUR", "USD"
34
+ end
35
+ RUBY
36
+ end
37
+
38
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
39
+ end
40
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#drop_enum" do
4
+ before_all { run_migration "create_schema :finances" }
5
+ before { run_migration(snippet) }
6
+
7
+ let(:snippet) do
8
+ <<~RUBY
9
+ create_enum "currency" do |e|
10
+ e.values "CHF", "EUR", "GBP", "USD", "JPY"
11
+ e.comment "Supported currencies"
12
+ end
13
+ RUBY
14
+ end
15
+ let(:query) { "SELECT 'USD'::currency;" }
16
+
17
+ context "with a full definition" do
18
+ let(:migration) do
19
+ <<~RUBY
20
+ drop_enum "currency" do |e|
21
+ e.values "CHF", "EUR", "GBP", "USD"
22
+ e.value "JPY"
23
+ e.comment "Supported currencies"
24
+ end
25
+ RUBY
26
+ end
27
+
28
+ its(:execution) { is_expected.to disable_sql_request(query) }
29
+ its(:execution) { is_expected.to remove(snippet).from_schema }
30
+ its(:inversion) { is_expected.to enable_sql_request(query) }
31
+ its(:inversion) { is_expected.not_to change_schema }
32
+ end
33
+
34
+ context "with a qualified name only" do
35
+ let(:migration) do
36
+ <<~RUBY
37
+ drop_enum "currency"
38
+ RUBY
39
+ end
40
+
41
+ its(:execution) { is_expected.to disable_sql_request(query) }
42
+ its(:execution) { is_expected.to remove(snippet).from_schema }
43
+ it { is_expected.to be_irreversible.because(/values can't be blank/i) }
44
+ end
45
+
46
+ context "when enum is used" do
47
+ before do
48
+ run_migration <<~RUBY
49
+ execute "CREATE TABLE sums (value integer, label currency);"
50
+ RUBY
51
+ end
52
+
53
+ context "without the `force` option" do
54
+ let(:migration) do
55
+ <<~RUBY
56
+ drop_enum "currency" do |e|
57
+ e.values "CHF", "EUR", "GBP", "USD"
58
+ e.value "JPY"
59
+ e.comment "Supported currencies"
60
+ end
61
+ RUBY
62
+ end
63
+
64
+ its(:execution) { is_expected.to raise_error(StandardError) }
65
+ end
66
+
67
+ context "with the `force: :cascade` option" do
68
+ let(:migration) do
69
+ <<~RUBY
70
+ drop_enum "currency", force: :cascade do |e|
71
+ e.values "CHF", "EUR", "GBP", "USD", "JPY"
72
+ e.comment "Supported currencies"
73
+ end
74
+ RUBY
75
+ end
76
+
77
+ its(:execution) { is_expected.to disable_sql_request(query) }
78
+ its(:execution) { is_expected.to remove(snippet).from_schema }
79
+ it { is_expected.to be_irreversible.because_of(/force: :cascade/i) }
80
+ end
81
+ end
82
+
83
+ context "when enum is absent" do
84
+ context "without the `force` option" do
85
+ let(:migration) do
86
+ <<~RUBY
87
+ drop_enum "foo"
88
+ RUBY
89
+ end
90
+
91
+ its(:execution) { is_expected.to raise_error(StandardError) }
92
+ end
93
+
94
+ context "with the `if_exists: true` option" do
95
+ let(:migration) do
96
+ <<~RUBY
97
+ drop_enum "foo", if_exists: true
98
+ RUBY
99
+ end
100
+
101
+ its(:execution) { is_expected.to enable_sql_request(query) }
102
+ its(:execution) { is_expected.not_to change_schema }
103
+ it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
104
+ end
105
+ end
106
+
107
+ context "without a name" do
108
+ let(:migration) do
109
+ <<~RUBY
110
+ drop_enum do |e|
111
+ e.values "CHF", "EUR", "GBP", "USD"
112
+ e.value "JPY"
113
+ e.comment "Supported currencies"
114
+ end
115
+ RUBY
116
+ end
117
+
118
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
119
+ end
120
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#rename_enum" do
4
+ before_all { run_migration "create_schema :finances" }
5
+ before { run_migration(old_snippet) }
6
+
7
+ let(:old_snippet) do
8
+ <<~RUBY
9
+ create_enum "currency" do |e|
10
+ e.values "CHF", "EUR", "GBP", "USD", "JPY"
11
+ e.comment "Supported currencies"
12
+ end
13
+ RUBY
14
+ end
15
+ let(:old_query) { "SELECT 'USD'::currency;" }
16
+
17
+ context "with new name and schema" do
18
+ let(:migration) do
19
+ <<~RUBY
20
+ rename_enum "currency", to: "finances.currency_value"
21
+ RUBY
22
+ end
23
+ let(:new_snippet) do
24
+ <<~RUBY
25
+ create_enum "finances.currency_value" do |e|
26
+ e.values "CHF", "EUR", "GBP", "USD", "JPY"
27
+ e.comment "Supported currencies"
28
+ end
29
+ RUBY
30
+ end
31
+ let(:new_query) { "SELECT 'USD'::finances.currency_value;" }
32
+
33
+ its(:execution) { is_expected.to enable_sql_request(new_query) }
34
+ its(:execution) { is_expected.to disable_sql_request(old_query) }
35
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
36
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
37
+
38
+ its(:inversion) { is_expected.to disable_sql_request(new_query) }
39
+ its(:inversion) { is_expected.to enable_sql_request(old_query) }
40
+ its(:inversion) { is_expected.not_to change_schema }
41
+ end
42
+
43
+ context "with the same name and schema" do
44
+ let(:migration) do
45
+ <<~RUBY
46
+ rename_enum "currency", to: "public.currency"
47
+ RUBY
48
+ end
49
+
50
+ it { is_expected.to fail_validation.because(/new name must be different/i) }
51
+ end
52
+
53
+ context "without new schema/name" do
54
+ let(:migration) do
55
+ <<~RUBY
56
+ rename_enum "currency"
57
+ RUBY
58
+ end
59
+
60
+ it { is_expected.to fail_validation.because(/new name can't be blank/i) }
61
+ end
62
+
63
+ context "without current name" do
64
+ let(:migration) do
65
+ <<~RUBY
66
+ rename_enum to: "finances.currency_value"
67
+ RUBY
68
+ end
69
+
70
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
71
+ end
72
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#add_foreign_key" do
4
+ before_all do
5
+ run_migration <<~RUBY
6
+ create_table :roles do |t|
7
+ t.string :name, index: { unique: true }
8
+ t.index %i[name id], unique: true
9
+ end
10
+
11
+ create_table :users do |t|
12
+ t.integer :role_id
13
+ t.string :role_name
14
+ t.string :role
15
+ end
16
+ RUBY
17
+ end
18
+
19
+ context "without options" do
20
+ let(:migration) do
21
+ <<~RUBY.squish
22
+ add_foreign_key "users", "roles"
23
+ RUBY
24
+ end
25
+ let(:invalid_query) do
26
+ <<~Q
27
+ -- breaks fk constraint
28
+ INSERT INTO users (role_id) VALUES (1);
29
+ Q
30
+ end
31
+ let(:valid_query) do
32
+ <<~Q
33
+ INSERT INTO roles (id) VALUES (1);
34
+ INSERT INTO users (role_id) VALUES (1);
35
+ Q
36
+ end
37
+
38
+ its(:execution) { is_expected.to enable_sql_request(valid_query) }
39
+ its(:execution) { is_expected.to disable_sql_request(invalid_query) }
40
+ its(:execution) { is_expected.to insert(migration).into_schema }
41
+
42
+ its(:inversion) { is_expected.not_to change_schema }
43
+ its(:inversion) { is_expected.to reenable_sql_request(invalid_query) }
44
+ end
45
+
46
+ context "with a custom primary key" do
47
+ let(:migration) do
48
+ <<~RUBY.squish
49
+ add_foreign_key "users", "roles", primary_key: "name"
50
+ RUBY
51
+ end
52
+ let(:invalid_query) do
53
+ <<~Q
54
+ -- breaks fk constraint
55
+ INSERT INTO users (role_name) VALUES ('admin');
56
+ Q
57
+ end
58
+ let(:valid_query) do
59
+ <<~Q
60
+ INSERT INTO roles (name) VALUES ('admin');
61
+ INSERT INTO users (role_name) VALUES ('admin');
62
+ Q
63
+ end
64
+
65
+ its(:execution) { is_expected.to enable_sql_request(valid_query) }
66
+ its(:execution) { is_expected.to disable_sql_request(invalid_query) }
67
+ its(:execution) { is_expected.to insert(migration).into_schema }
68
+
69
+ its(:inversion) { is_expected.to reenable_sql_request(invalid_query) }
70
+ its(:inversion) { is_expected.not_to change_schema }
71
+ end
72
+
73
+ context "with a custom name" do
74
+ let(:migration) do
75
+ <<~RUBY.squish
76
+ add_foreign_key "users", "roles", name: "user_role_fk"
77
+ RUBY
78
+ end
79
+
80
+ its(:execution) { is_expected.to insert(migration).into_schema }
81
+ its(:inversion) { is_expected.not_to change_schema }
82
+ end
83
+
84
+ context "with cascades" do
85
+ let(:migration) do
86
+ <<~RUBY.squish
87
+ add_foreign_key "users", "roles",
88
+ on_update: :cascade,
89
+ on_delete: :restrict
90
+ RUBY
91
+ end
92
+
93
+ its(:execution) { is_expected.to insert(migration).into_schema }
94
+ its(:inversion) { is_expected.not_to change_schema }
95
+ end
96
+
97
+ context "with a multi-key constraint" do
98
+ let(:migration) do
99
+ <<~RUBY.squish
100
+ add_foreign_key "users", "roles",
101
+ primary_key: %w[name id],
102
+ match: :full
103
+ RUBY
104
+ end
105
+ let(:invalid_query) do
106
+ <<~Q
107
+ -- breaks fk constraint
108
+ INSERT INTO roles (id, name) VALUES (1, 'admin'), (2, 'developer');
109
+ INSERT INTO users (role_id, role_name) VALUES (1, 'developer');
110
+ Q
111
+ end
112
+ let(:valid_query) do
113
+ <<~Q
114
+ INSERT INTO roles (id, name) VALUES (1, 'developer');
115
+ INSERT INTO users (role_id, role_name) VALUES (1, 'developer');
116
+ Q
117
+ end
118
+
119
+ its(:execution) { is_expected.to enable_sql_request(valid_query) }
120
+ its(:execution) { is_expected.to disable_sql_request(invalid_query) }
121
+ its(:execution) { is_expected.to insert(migration).into_schema }
122
+
123
+ its(:inversion) { is_expected.to reenable_sql_request(invalid_query) }
124
+ its(:inversion) { is_expected.not_to change_schema }
125
+ end
126
+
127
+ context "with a custom multi-key constraint" do
128
+ let(:migration) do
129
+ <<~RUBY.squish
130
+ add_foreign_key "users", "roles",
131
+ columns: %w[role role_id],
132
+ primary_key: %w[name id],
133
+ match: :full
134
+ RUBY
135
+ end
136
+
137
+ its(:execution) { is_expected.to insert(migration).into_schema }
138
+ its(:inversion) { is_expected.not_to change_schema }
139
+ end
140
+
141
+ context "with a comment" do
142
+ let(:migration) do
143
+ <<~RUBY
144
+ add_foreign_key "users", "roles", comment: "The user's role"
145
+ RUBY
146
+ end
147
+
148
+ its(:execution) { is_expected.to insert(migration).into_schema }
149
+ its(:inversion) { is_expected.not_to change_schema }
150
+ end
151
+
152
+ context "when foreign key already existed" do
153
+ before do
154
+ run_migration <<~RUBY
155
+ add_foreign_key "users", "roles"
156
+ RUBY
157
+ end
158
+
159
+ context "without the `if_not_exists` option" do
160
+ let(:migration) do
161
+ <<~RUBY
162
+ add_foreign_key "users", "roles"
163
+ RUBY
164
+ end
165
+
166
+ its(:execution) { is_expected.to raise_error(StandardError) }
167
+ end
168
+
169
+ context "with the `if_not_exists: true` option" do
170
+ let(:migration) do
171
+ <<~RUBY
172
+ add_foreign_key "users", "roles", if_not_exists: true
173
+ RUBY
174
+ end
175
+
176
+ its(:execution) { is_expected.not_to raise_error }
177
+ it { is_expected.to be_irreversible.because_of(/if_not_exists: true/i) }
178
+ end
179
+ end
180
+
181
+ context "without a reference" do
182
+ let(:migration) do
183
+ <<~RUBY.squish
184
+ add_foreign_key "users"
185
+ RUBY
186
+ end
187
+
188
+ it { is_expected.to fail_validation.because(/reference can't be blank/i) }
189
+ end
190
+
191
+ context "inside table definition" do
192
+ let(:migration) do
193
+ <<~RUBY
194
+ change_table :users do |t|
195
+ t.foreign_key "roles"
196
+ end
197
+ RUBY
198
+ end
199
+ let(:snippet) do
200
+ <<~RUBY.squish
201
+ add_foreign_key "users", "roles"
202
+ RUBY
203
+ end
204
+
205
+ its(:execution) { is_expected.to insert(snippet).into_schema }
206
+ its(:inversion) { is_expected.not_to change_schema }
207
+ end
208
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#drop_foreign_key" do
4
+ before_all do
5
+ run_migration <<~RUBY
6
+ create_table :roles do |t|
7
+ t.string :name, index: { unique: true }
8
+ end
9
+
10
+ create_table :users do |t|
11
+ t.string :role
12
+ end
13
+ RUBY
14
+ end
15
+
16
+ context "when fk was given an explicit name" do
17
+ before { run_migration(snippet) }
18
+
19
+ let(:snippet) do
20
+ <<~RUBY.squish
21
+ add_foreign_key "users", "roles",
22
+ column: "role",
23
+ primary_key: "name",
24
+ name: "my_fk"
25
+ RUBY
26
+ end
27
+ let(:invalid_query) do
28
+ <<~Q
29
+ INSERT INTO users (role) VALUES ('admin');
30
+ Q
31
+ end
32
+ let(:valid_query) do
33
+ <<~Q
34
+ INSERT INTO roles (name) VALUES ('admin');
35
+ INSERT INTO users (role) VALUES ('admin');
36
+ Q
37
+ end
38
+
39
+ context "when identified by a name only" do
40
+ let(:migration) do
41
+ <<~RUBY
42
+ drop_foreign_key "users", name: "my_fk"
43
+ RUBY
44
+ end
45
+
46
+ its(:execution) { is_expected.to enable_sql_request(valid_query) }
47
+ its(:execution) { is_expected.to reenable_sql_request(invalid_query) }
48
+ its(:execution) { is_expected.to remove(snippet).from_schema }
49
+
50
+ it { is_expected.to be_irreversible.because(/reference can't be blank/) }
51
+ end
52
+
53
+ context "with all attributes of the constraint" do
54
+ let(:migration) do
55
+ <<~RUBY
56
+ drop_foreign_key "users", "roles",
57
+ column: "role",
58
+ primary_key: "name",
59
+ name: "my_fk"
60
+ RUBY
61
+ end
62
+
63
+ its(:execution) { is_expected.to enable_sql_request(valid_query) }
64
+ its(:execution) { is_expected.to reenable_sql_request(invalid_query) }
65
+ its(:execution) { is_expected.to remove(snippet).from_schema }
66
+
67
+ its(:inversion) { is_expected.to disable_sql_request(invalid_query) }
68
+ its(:inversion) { is_expected.not_to change_schema }
69
+ end
70
+
71
+ context "without a name" do
72
+ let(:migration) do
73
+ <<~RUBY
74
+ drop_foreign_key "users"
75
+ RUBY
76
+ end
77
+
78
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
79
+ end
80
+
81
+ context "without a table" do
82
+ let(:migration) do
83
+ <<~RUBY
84
+ drop_foreign_key name: "my_fk"
85
+ RUBY
86
+ end
87
+
88
+ it { is_expected.to fail_validation.because(/table can't be blank/i) }
89
+ end
90
+ end
91
+
92
+ context "when fk got a generated name outside of a table" do
93
+ before { run_migration(snippet) }
94
+
95
+ let(:snippet) do
96
+ <<~RUBY.squish
97
+ add_foreign_key "users", "roles",
98
+ column: "role",
99
+ primary_key: "name"
100
+ RUBY
101
+ end
102
+
103
+ context "with all attributes of the constraint" do
104
+ let(:migration) do
105
+ <<~RUBY
106
+ drop_foreign_key "users", "roles",
107
+ column: "role",
108
+ primary_key: "name"
109
+ RUBY
110
+ end
111
+
112
+ its(:execution) { is_expected.to remove(snippet).from_schema }
113
+ its(:inversion) { is_expected.not_to change_schema }
114
+ end
115
+ end
116
+
117
+ context "when fk got a generated name inside a table" do
118
+ before do
119
+ run_migration <<~RUBY
120
+ change_table :users do |t|
121
+ t.foreign_key :roles, column: :role, primary_key: :name
122
+ end
123
+ RUBY
124
+ end
125
+
126
+ let(:migration) do
127
+ <<~RUBY
128
+ drop_foreign_key "users", "roles",
129
+ column: "role",
130
+ primary_key: "name"
131
+ RUBY
132
+ end
133
+ let(:snippet) do
134
+ <<~RUBY.squish
135
+ add_foreign_key "users", "roles",
136
+ column: "role",
137
+ primary_key: "name"
138
+ RUBY
139
+ end
140
+
141
+ its(:execution) { is_expected.to remove(snippet).from_schema }
142
+ its(:inversion) { is_expected.not_to change_schema }
143
+ end
144
+
145
+ context "when a key not existed" do
146
+ context "without the `it_exists: true` option" do
147
+ let(:migration) do
148
+ <<~RUBY.squish
149
+ drop_foreign_key :users, :roles, name: "unknown"
150
+ RUBY
151
+ end
152
+
153
+ its(:execution) { is_expected.to raise_error(StandardError) }
154
+ end
155
+
156
+ context "with the `it_exists: true` option" do
157
+ let(:migration) do
158
+ <<~RUBY
159
+ drop_foreign_key :users, :roles, name: :unknown, if_exists: true
160
+ RUBY
161
+ end
162
+
163
+ its(:execution) { is_expected.not_to change_schema }
164
+ it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#rename_foreign_key" do
4
+ before_all do
5
+ run_migration <<~RUBY
6
+ create_table :roles do |t|
7
+ t.string :name
8
+ t.index %i[name id], unique: true
9
+ end
10
+
11
+ create_table :users do |t|
12
+ t.integer :role_id
13
+ t.string :role_name
14
+ end
15
+ RUBY
16
+ end
17
+ before { run_migration(old_snippet) }
18
+
19
+ context "when the key had an explicit name" do
20
+ let(:old_snippet) do
21
+ <<~RUBY.squish
22
+ add_foreign_key "users", "roles",
23
+ primary_key: %w[name id],
24
+ name: "my_key"
25
+ RUBY
26
+ end
27
+
28
+ context "when both names are explicitly specified" do
29
+ let(:migration) do
30
+ <<~RUBY.squish
31
+ rename_foreign_key "users", name: "my_key", to: "my_new_key"
32
+ RUBY
33
+ end
34
+ let(:new_snippet) do
35
+ <<~RUBY.squish
36
+ add_foreign_key "users", "roles",
37
+ primary_key: %w[name id],
38
+ name: "my_new_key"
39
+ RUBY
40
+ end
41
+
42
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
43
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
44
+ its(:reversion) { is_expected.not_to change_schema }
45
+ end
46
+
47
+ context "when new name is not specified" do
48
+ let(:migration) do
49
+ <<~RUBY.snippet
50
+ rename_foreign_key "users", name: "my_key"
51
+ RUBY
52
+ end
53
+ let(:snippet) do
54
+ <<~RUBY.snippet
55
+ add_foreign_key "users", "roles",
56
+ columns: %i[role_name role_id],
57
+ primary_key: %i[name id]
58
+ RUBY
59
+ end
60
+ end
61
+
62
+ context "without the old name" do
63
+ let(:migration) do
64
+ <<~RUBY.squish
65
+ rename_foreign_key "users", "roles", to: "my_new_key"
66
+ RUBY
67
+ end
68
+
69
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
70
+ end
71
+ end
72
+
73
+ context "when the key was anonymous" do
74
+ let(:old_snippet) do
75
+ <<~RUBY.squish
76
+ add_foreign_key "users", "roles", primary_key: %w[name id]
77
+ RUBY
78
+ end
79
+
80
+ context "with a new name" do
81
+ let(:migration) do
82
+ <<~RUBY.squish
83
+ rename_foreign_key "users", "roles",
84
+ primary_key: %w[name id],
85
+ to: "my_key"
86
+ RUBY
87
+ end
88
+ let(:new_snippet) do
89
+ <<~RUBY.squish
90
+ add_foreign_key "users", "roles",
91
+ primary_key: %w[name id],
92
+ name: "my_key"
93
+ RUBY
94
+ end
95
+
96
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
97
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
98
+ its(:inversion) { is_expected.not_to change_schema }
99
+ end
100
+ end
101
+ end