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,376 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Helpers for migration end-to-end specs
4
+ module MigrationsHelper
5
+ extend self
6
+
7
+ # Wrap the migration containing ruby code for a `change` method
8
+ # into the corresponding migrator.
9
+ #
10
+ # Methods `execution` and `inversion` return blocks
11
+ # running the migration up or up'n'down correspondingly.
12
+ # As a result the instance of the class can be used as a subject
13
+ # of the test:
14
+ #
15
+ # subject { TestMigration.new(migration) }
16
+ # let(:migration) { "puts 'FOOBAR'" }
17
+ # its(:execution) { is_expected.not_to raise_error }
18
+ #
19
+ class TestMigration
20
+ private def initialize(change, args = {})
21
+ @change = change.to_s
22
+ @verbose = args[:verbose]
23
+ end
24
+
25
+ def execution
26
+ proc { run_migration(:up) }
27
+ end
28
+
29
+ def inversion
30
+ proc { run_migration(:up, :down) }
31
+ end
32
+ alias reversion inversion
33
+
34
+ private
35
+
36
+ MIGRATION =
37
+ if Rails::VERSION::MAJOR >= 5
38
+ ::ActiveRecord::Migration[ActiveRecord::Migration.current_version]
39
+ else
40
+ ::ActiveRecord::Migration
41
+ end
42
+
43
+ def version
44
+ @version ||= (Time.now.to_f * 1e6).to_i
45
+ end
46
+
47
+ def migration_klass
48
+ @migration_klass ||= Class.new(MIGRATION).tap do |m|
49
+ m.class_eval("def change;#{@change};end", __FILE__, __LINE__)
50
+ end
51
+ end
52
+
53
+ def migration
54
+ @migration ||= migration_klass.new("migration", version)
55
+ end
56
+
57
+ def run_migration(*directions)
58
+ return run_and_report(*directions) if @verbose
59
+
60
+ silence_stream($stdout) { run_and_report(*directions) }
61
+ end
62
+
63
+ def run_and_report(*directions)
64
+ Array.wrap(directions).each do |direction|
65
+ ActiveRecord::Migrator
66
+ .new(direction, [migration], ActiveRecord::SchemaMigration, version)
67
+ .run
68
+ end
69
+ end
70
+
71
+ def silence_stream(stream)
72
+ old_stream = stream.dup
73
+ stream.reopen(IO::NULL)
74
+ stream.sync = true
75
+ yield
76
+ ensure
77
+ stream.reopen(old_stream)
78
+ old_stream.close
79
+ end
80
+ end
81
+
82
+ def run_migration(snippet)
83
+ data = self.class.metadata.slice(:verbose)
84
+ TestMigration.new(snippet, data).execution.call
85
+ end
86
+
87
+ # Default subject for migration tests
88
+ # It expect the migration to be specified (in a `let` clause)
89
+ # @return [TestMigration]
90
+ def subject
91
+ @subject ||= begin
92
+ raise NoMethodError, <<~MSG.squish unless respond_to?(:migration)
93
+ Use `let(:migration) { ... }` with a Ruby code for migration
94
+ MSG
95
+
96
+ data = self.class.metadata.slice(:verbose)
97
+ TestMigration.new(migration, data)
98
+ end
99
+ end
100
+
101
+ # Read the database schema
102
+ # @return [String]
103
+ def read_schema(skip_header: false)
104
+ stream = StringIO.new
105
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
106
+ stream = stream.string.lines.map(&:rstrip).join("\n")
107
+ return stream unless skip_header
108
+
109
+ stream.lines.reject { |line| line["ActiveRecord::Schema.define"] }.join
110
+ end
111
+ end
112
+
113
+ # expect { ... }.not_to change_schema
114
+ RSpec::Matchers.define :change_schema do
115
+ supports_block_expectations
116
+
117
+ match_when_negated do |block|
118
+ @expected = MigrationsHelper.read_schema(skip_header: true)
119
+ block.call
120
+ @actual = MigrationsHelper.read_schema(skip_header: true)
121
+ expect(@actual).to eq(@expected)
122
+ end
123
+
124
+ failure_message_when_negated do
125
+ differ = RSpec::Support::Differ.new(color: true)
126
+ <<~MSG
127
+ It is expected the schema to remain the same, but it has been changed:
128
+
129
+ #{differ.diff_as_string(@actual, @expected).indent(2)}
130
+ MSG
131
+ end
132
+ end
133
+
134
+ # expect { ... }.to insert(snippet).into_schema
135
+ # expect { ... }.to insert(snippet).into_schema
136
+ RSpec::Matchers.define :insert do |snippet|
137
+ supports_block_expectations
138
+
139
+ description do
140
+ "insert given snippet into the database schema"
141
+ end
142
+
143
+ attr_reader :strict
144
+
145
+ chain(:into_schema) {} # does nothing; added for readability
146
+
147
+ match do |block|
148
+ @expected = snippet.lines.map(&:rstrip).join("\n") << "\n"
149
+ @expected = @expected.indent(2) unless @expected[/\A /]
150
+
151
+ @final = false
152
+ @actual = MigrationsHelper.read_schema
153
+ expect(@actual).not_to include(@expected)
154
+
155
+ block.call
156
+
157
+ @final = true
158
+ @actual = MigrationsHelper.read_schema
159
+ expect(@actual).to include(@expected)
160
+ end
161
+
162
+ failure_message do
163
+ header = <<~MSG
164
+ It is expected the following snippet to be added to the schema:
165
+
166
+ #{@expected}
167
+ MSG
168
+
169
+ return <<~MSG unless @final
170
+ #{header.strip}
171
+
172
+ But it was present from the very beginning:
173
+
174
+ #{@actual}
175
+ MSG
176
+
177
+ differ = RSpec::Support::Differ.new(color: true)
178
+ <<~MSG
179
+ #{header.strip}
180
+
181
+ But the final snippet is different:
182
+
183
+ #{differ.diff_as_string(@actual, @expected).indent(2)}
184
+ MSG
185
+ end
186
+ end
187
+
188
+ # expect { ... }.to remove(snippet).from_schema
189
+ # expect { ... }.to remove(snippet).from_schema
190
+ RSpec::Matchers.define :remove do |snippet|
191
+ supports_block_expectations
192
+
193
+ description do
194
+ "insert given snippet into the database schema"
195
+ end
196
+
197
+ attr_reader :strict
198
+
199
+ chain(:from_schema) {} # does nothing; added for readability
200
+
201
+ match do |block|
202
+ @expected = snippet.lines.map(&:rstrip).join("\n") << "\n"
203
+ @expected = @expected.indent(2) unless @expected[/\A /]
204
+
205
+ @final = false
206
+ @actual = MigrationsHelper.read_schema
207
+ expect(@actual).to include(@expected)
208
+
209
+ block.call
210
+
211
+ @final = true
212
+ @actual = MigrationsHelper.read_schema
213
+ expect(@actual).not_to include(@expected)
214
+ end
215
+
216
+ failure_message do
217
+ header = <<~MSG
218
+ It is expected the following snippet to be removed from the schema:
219
+
220
+ #{@expected}
221
+ MSG
222
+
223
+ return <<~MSG if @final
224
+ #{header}
225
+
226
+ But it is still present in the schema:
227
+
228
+ #{@actual}
229
+ MSG
230
+
231
+ differ = RSpec::Support::Differ.new(color: true)
232
+ <<~MSG
233
+ #{header.strip}
234
+
235
+ But the initial schema was different:
236
+
237
+ #{differ.diff_as_string(@actual, @expected).indent(2)}
238
+ MSG
239
+ end
240
+ end
241
+
242
+ # expect { ... }.to enable_sql_request(query)
243
+ #
244
+ # Because we use a `transactional` strategy of the database cleaner,
245
+ # the whole migration is wrapped into the transaction which won't
246
+ # be fixed, but rolled out at the end of each spec.
247
+ #
248
+ # In this case some queries provide false negatives
249
+ # because they expect a DDL transaction to be fixed
250
+ # before running a select. For this reason we enable
251
+ # to ignore particular errors, treating them as positive outcomes.
252
+ #
253
+ # expect { ... }.to enable_sql_request(query).ignoring(/values/i)
254
+ RSpec::Matchers.define :enable_sql_request do |query|
255
+ supports_block_expectations
256
+
257
+ description do
258
+ "enable valid SQL request"
259
+ end
260
+
261
+ chain(:ignoring, :pattern)
262
+
263
+ match do |block|
264
+ block.call
265
+ check = proc { ActiveRecord::Base.connection.execute(query) }
266
+
267
+ if pattern
268
+ expect(&check).to raise_error(pattern)
269
+ else
270
+ expect(&check).not_to raise_error
271
+ end
272
+ end
273
+ end
274
+
275
+ RSpec::Matchers.alias_matcher :reenable_sql_request, :enable_sql_request do
276
+ "enable SQL request as valid again"
277
+ end
278
+
279
+ # expect { ... }.to disable_sql_request(query)
280
+ RSpec::Matchers.define :disable_sql_request do |query|
281
+ supports_block_expectations
282
+
283
+ description do
284
+ "disable SQL request as invalid"
285
+ end
286
+
287
+ match do |block|
288
+ block.call
289
+ expect { ActiveRecord::Base.connection.execute(query) }.to raise_error(StandardError)
290
+ end
291
+ end
292
+
293
+ # expect { ... }.to fail_validation.because_of(/name/i)
294
+ RSpec::Matchers.define :fail_validation do
295
+ chain(:because_of) { |regexp| @pattern = regexp }
296
+ chain(:because) { |regexp| @pattern = regexp }
297
+
298
+ attr_reader :pattern
299
+
300
+ match do |test_migration|
301
+ @actual = nil
302
+ expect { test_migration.execution.call }.to raise_error do |ex|
303
+ @actual = ex
304
+ expect(ex).to be_a(StandardError)
305
+ expect(ex.cause).to be_a(ActiveModel::ValidationError)
306
+ expect(ex.message).to match(pattern) if pattern
307
+ end
308
+ end
309
+
310
+ failure_message do
311
+ message = ["It is expected the migration to fail validation"]
312
+ message << "for the following reason: #{pattern}" if pattern
313
+ if @actual.nil?
314
+ message << "but it proves valid"
315
+ elsif @actual.cause.is_a?(ActiveModel::ValidationError)
316
+ message << "but the actual reason is different:"
317
+ message << @actual.cause.message
318
+ else
319
+ message << "but it has risen the following exception:"
320
+ message << @actual.inspect
321
+ message << @actual.backtrace.join("\n")
322
+ end
323
+ message.join("\n")
324
+ end
325
+ end
326
+
327
+ # expect { ... }.to be_irreversible.because_of(/added values/i)
328
+ RSpec::Matchers.define :be_irreversible do
329
+ description do
330
+ "be irreversible migration"
331
+ end
332
+
333
+ chain(:because_of) { |regexp| @pattern = regexp }
334
+ chain(:because) { |regexp| @pattern = regexp }
335
+
336
+ attr_reader :pattern
337
+
338
+ match do |test_migration|
339
+ @actual = nil
340
+ expect { test_migration.inversion.call }.to raise_error do |ex|
341
+ @actual = ex
342
+ expect(ex).to be_a(StandardError)
343
+ expect(ex.cause).to be_a(ActiveRecord::IrreversibleMigration)
344
+ next unless pattern
345
+
346
+ expect(ex.message).to match(pattern)
347
+ end
348
+ end
349
+
350
+ failure_message do
351
+ message = ["It is expected the migration to be irreversible"]
352
+ message << "for the following reason: #{pattern}" if pattern
353
+
354
+ if @actual.nil?
355
+ message << "but it proves reversible"
356
+ elsif @actual.cause.is_a?(ActiveRecord::IrreversibleMigration)
357
+ message << "but the actual reason is different:"
358
+ message << @actual.message
359
+ else
360
+ message << "but it has risen the following exception:"
361
+ message << @actual.inspect
362
+ end
363
+ message.join("\n")
364
+ end
365
+ end
366
+
367
+ RSpec.configure do |config|
368
+ migration = { described_class: ActiveRecord::Migration }
369
+ config.include MigrationsHelper, **migration
370
+ config.around(:each, **migration) do |example|
371
+ DatabaseCleaner.start
372
+ ActiveRecord::SchemaMigration.create_table
373
+ example.run
374
+ DatabaseCleaner.clean
375
+ end
376
+ end