nandi 0.9.0 → 0.11.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba61fdd052ec58212031846c92ad947cb1f7cb8c033271b101f5973a2b7074f6
4
- data.tar.gz: b0c30216fb5544927d1d3b47ef0ad87d935a9ea8859c37a8779662a65f88d9f5
3
+ metadata.gz: 78ca004c815e5952f1fc51b44fa839afd81b1297fc3079e98202719eead8ac74
4
+ data.tar.gz: 3e19d0265b7e428298abb84c78f463756437c431a2757abe8d28ed232f7c6aab
5
5
  SHA512:
6
- metadata.gz: f4c0ecfcb5e1a84c4a8b2e0721f4e48a8abff3f6bde78f04184544e9004987a4f155eebb3342b780fc793bc2d5104a8a98d7d25c6fcc9f8da43cdd70e0b5f9fd
7
- data.tar.gz: aedffb405097c5d9e988e001eab0c929ed0fab64b1b9b04a534051fa59aa16634c3c9071bf5daf31b7c416bfa94679b020685bd8c201c78d0552ebbcf60e0a75
6
+ metadata.gz: fa56e2901b20d88d59bd0cf05b4b78395fc39a3c8c0e7d80484fbc99045c3d8f099861b36a2220b5a183473a4de4f583f873510abe78f469ef700e5b1c6e5035
7
+ data.tar.gz: 91837a550c6deb7b724d5778300821cd73519cadc2e9b5999645d5884f7a1b17ea9810c24ce5ecfc3431e9fcf0ae260b1de283c6ccd0ea6828d33b7d1ffe33e0
data/README.md CHANGED
@@ -4,7 +4,7 @@ Friendly Postgres migrations for people who don't want to take down their databa
4
4
 
5
5
  ## Supported
6
6
 
7
- - Ruby 2.4 or above
7
+ - Ruby 2.5 or above
8
8
  - Rails 5.2 or above
9
9
  - Postgres 11 or above
10
10
 
@@ -202,10 +202,10 @@ class AddReferenceOnFoosToBars < ActiveRecord::Migration[5.2]
202
202
  set_statement_timeout(1_500)
203
203
 
204
204
  def up
205
- add_reference(:foos, :bar)
205
+ add_column(:foos, :bar_id, :bigint)
206
206
  end
207
207
  def down
208
- remove_reference(:foos, :bar)
208
+ remove_column(:foos, :bar_id)
209
209
  end
210
210
  end
211
211
 
@@ -268,10 +268,9 @@ Add a foreign key constraint. The generated SQL will include the NOT VALID param
268
268
  ### `#add_index(table, fields, **kwargs)`
269
269
  Adds a new index to the database.
270
270
 
271
- Nandi will:
272
-
271
+ Nandi will
273
272
  - add the `CONCURRENTLY` option, which means the change takes a less restrictive lock at the cost of not running in a DDL transaction
274
- - use the `BTREE` index type which is the safest to create.
273
+ - default to the `BTREE` index type, as it is commonly a good fit.
275
274
 
276
275
  Because index creation is particularly failure-prone, and because we cannot run in a transaction and therefore risk partially applied migrations that (in a Rails environment) require manual intervention, Nandi Validates that, if there is a add_index statement in the migration, it must be the only statement.
277
276
 
@@ -286,6 +285,12 @@ create_table :widgets do |t|
286
285
  end
287
286
  ```
288
287
 
288
+ ### `#add_reference(table, ref_name, **extra_args)`
289
+ Adds a new reference column. Nandi will validate that the foreign key flag is not set to true; use `add_foreign_key` and `validate_foreign_key` instead! Nandi will also set the `index: false` flag, as index creation is unsafe unless done concurrently in a separate migration.
290
+
291
+ ### `#remove_reference(table, ref_name, **extra_args)`
292
+ Removes a reference column.
293
+
289
294
  ### `#remove_column(table, name, **extra_args)`
290
295
  Remove an existing column.
291
296
 
data/exe/nandi-enforce CHANGED
@@ -31,6 +31,6 @@ OptionParser.new do |o|
31
31
  end
32
32
  end.parse!
33
33
 
34
- enforcer = Nandi::SafeMigrationEnforcer.new(opts)
34
+ enforcer = Nandi::SafeMigrationEnforcer.new(**opts)
35
35
 
36
36
  enforcer.run
@@ -64,7 +64,7 @@ module Nandi
64
64
  key = if k.is_a?(Symbol)
65
65
  symbol_key(k)
66
66
  else
67
- format_value(k) + " =>"
67
+ "#{format_value(k)} =>"
68
68
  end
69
69
  "#{key} #{format_value(v)}"
70
70
  end
@@ -21,11 +21,10 @@ module Nandi
21
21
  name: name,
22
22
 
23
23
  # Overrides and extra options
24
- **@extra_args,
24
+ **extra_args_with_default_index_type,
25
25
 
26
26
  # Mandatory values
27
27
  algorithm: :concurrently,
28
- using: :btree,
29
28
  }
30
29
  end
31
30
 
@@ -38,13 +37,19 @@ module Nandi
38
37
  private
39
38
 
40
39
  def name
41
- :"idx_#{table.to_s}_on_#{field_names}"
40
+ :"idx_#{table}_on_#{field_names}"
42
41
  end
43
42
 
44
43
  def field_names
45
44
  field_names = fields.respond_to?(:map) ? fields.map(&:to_s).join("_") : fields
46
45
  field_names.to_s.scan(/\w+/).join("_")
47
46
  end
47
+
48
+ def extra_args_with_default_index_type
49
+ {
50
+ using: :btree,
51
+ }.merge(@extra_args)
52
+ end
48
53
  end
49
54
  end
50
55
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nandi
4
+ module Instructions
5
+ class AddReference
6
+ DEFAULT_EXTRA_ARGS = { index: false }.freeze
7
+ attr_reader :table, :ref_name, :extra_args
8
+
9
+ def initialize(table:, ref_name:, **kwargs)
10
+ @table = table
11
+ @ref_name = ref_name
12
+ @extra_args = DEFAULT_EXTRA_ARGS.merge(kwargs)
13
+ end
14
+
15
+ def procedure
16
+ :add_reference
17
+ end
18
+
19
+ def lock
20
+ Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE
21
+ end
22
+ end
23
+ end
24
+ end
@@ -8,7 +8,12 @@ module Nandi
8
8
  def initialize(table:, name:, **extra_args)
9
9
  @table = table
10
10
  @name = name
11
- @extra_args = extra_args if extra_args.any?
11
+ @extra_args =
12
+ if extra_args.any?
13
+ extra_args
14
+ else
15
+ {}
16
+ end
12
17
  end
13
18
 
14
19
  def procedure
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nandi
4
+ module Instructions
5
+ class RemoveReference
6
+ attr_reader :table, :ref_name, :extra_args
7
+
8
+ def initialize(table:, ref_name:, **kwargs)
9
+ @table = table
10
+ @ref_name = ref_name
11
+ @extra_args = kwargs
12
+ end
13
+
14
+ def procedure
15
+ :remove_reference
16
+ end
17
+
18
+ def lock
19
+ Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE
20
+ end
21
+ end
22
+ end
23
+ end
@@ -5,6 +5,8 @@ require "nandi/instructions/remove_index"
5
5
  require "nandi/instructions/create_table"
6
6
  require "nandi/instructions/drop_table"
7
7
  require "nandi/instructions/add_column"
8
+ require "nandi/instructions/add_reference"
9
+ require "nandi/instructions/remove_reference"
8
10
  require "nandi/instructions/remove_column"
9
11
  require "nandi/instructions/add_foreign_key"
10
12
  require "nandi/instructions/drop_constraint"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/hash/indifferent_access"
4
+ require "digest"
4
5
 
5
6
  module Nandi
6
7
  class Lockfile
@@ -42,7 +43,26 @@ module Nandi
42
43
  end
43
44
 
44
45
  def persist!
45
- File.write(path, lockfile.to_h.deep_stringify_keys.to_yaml)
46
+ # This is a somewhat ridiculous trick to avoid merge conflicts in git.
47
+ #
48
+ # Normally, new migrations are added to the bottom of the Nandi lockfile.
49
+ # This is relatively unfriendly to git's merge algorithm, and means that
50
+ # if someone merges a pull request with a completely unrelated migration,
51
+ # you'll have to rebase to get yours merged as the last line of the file
52
+ # will be seen as a conflict (both branches added content there).
53
+ #
54
+ # This is in contrast to something like Gemfile.lock, where changes tend
55
+ # to be distributed throughout the file. The idea behind sorting by
56
+ # SHA-256 hash is to distribute new Nandi lockfile entries evenly, but
57
+ # also stably through the file. It needs to be stable or we'd have even
58
+ # worse merge conflict problems (e.g. if we randomised the order on
59
+ # writing the file, the whole thing would conflict pretty much every time
60
+ # it was regenerated).
61
+ content = Hash[lockfile.to_h.deep_stringify_keys.sort_by do |k, _|
62
+ Digest::SHA256.hexdigest(k)
63
+ end].to_yaml
64
+
65
+ File.write(path, content)
46
66
  end
47
67
 
48
68
  def path
@@ -43,6 +43,7 @@ module Nandi
43
43
 
44
44
  class << self
45
45
  attr_reader :lock_timeout, :statement_timeout
46
+
46
47
  # For sake both of correspondence with Postgres syntax and familiarity
47
48
  # with activerecord-safe_migrations's identically named macros, we
48
49
  # disable this cop.
@@ -111,7 +112,7 @@ module Nandi
111
112
  # Nandi will:
112
113
  # * add the `CONCURRENTLY` option, which means the change takes a less
113
114
  # restrictive lock at the cost of not running in a DDL transaction
114
- # * use the `BTREE` index type which is the safest to create.
115
+ # * default to the `BTREE` index type, as it is commonly a good fit.
115
116
  #
116
117
  # Because index creation is particularly failure-prone, and because
117
118
  # we cannot run in a transaction and therefore risk partially applied
@@ -188,6 +189,31 @@ module Nandi
188
189
  )
189
190
  end
190
191
 
192
+ # Adds a new reference column. Nandi will validate that the foreign key flag
193
+ # is not set to true; use `add_foreign_key` and `validate_foreign_key` instead!
194
+ # @param table [Symbol, String] The name of the table to add the column to
195
+ # @param ref_name [Symbol, String] The referenced column name
196
+ # @param kwargs [Hash] Arbitrary options to be passed to the backend.
197
+ def add_reference(table, ref_name, **kwargs)
198
+ current_instructions << Instructions::AddReference.new(
199
+ table: table,
200
+ ref_name: ref_name,
201
+ **kwargs,
202
+ )
203
+ end
204
+
205
+ # Removes a reference column.
206
+ # @param table [Symbol, String] The name of the table to remove the reference from
207
+ # @param ref_name [Symbol, String] The referenced column name
208
+ # @param kwargs [Hash] Arbitrary options to be passed to the backend.
209
+ def remove_reference(table, ref_name, **kwargs)
210
+ current_instructions << Instructions::RemoveReference.new(
211
+ table: table,
212
+ ref_name: ref_name,
213
+ **kwargs,
214
+ )
215
+ end
216
+
191
217
  # Remove an existing column.
192
218
  # @param table [Symbol, String] The name of the table to remove the column
193
219
  # from.
@@ -331,9 +357,9 @@ module Nandi
331
357
  end.uniq
332
358
  end
333
359
 
334
- def method_missing(name, *args, &block)
360
+ def method_missing(name, *args, **kwargs, &block)
335
361
  if Nandi.config.custom_methods.key?(name)
336
- invoke_custom_method(name, *args, &block)
362
+ invoke_custom_method(name, *args, **kwargs, &block)
337
363
  else
338
364
  super
339
365
  end
@@ -355,9 +381,9 @@ module Nandi
355
381
  Nandi.config.access_exclusive_lock_timeout
356
382
  end
357
383
 
358
- def invoke_custom_method(name, *args, &block)
384
+ def invoke_custom_method(name, *args, **kwargs, &block)
359
385
  klass = Nandi.config.custom_methods[name]
360
- current_instructions << klass.new(*args, &block)
386
+ current_instructions << klass.new(*args, **kwargs, &block)
361
387
  end
362
388
  end
363
389
  end
@@ -82,6 +82,18 @@ module Nandi
82
82
  formatted_property :extra_args
83
83
  end
84
84
 
85
+ class AddReferenceCell < Base
86
+ formatted_property :table
87
+ formatted_property :ref_name
88
+ formatted_property :extra_args
89
+ end
90
+
91
+ class RemoveReferenceCell < Base
92
+ formatted_property :table
93
+ formatted_property :ref_name
94
+ formatted_property :extra_args
95
+ end
96
+
85
97
  class RemoveColumnCell < Base
86
98
  formatted_property :table
87
99
  formatted_property :name
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nandi/validation/failure_helpers"
4
+
5
+ module Nandi
6
+ module Validation
7
+ class AddReferenceValidator
8
+ include Nandi::Validation::FailureHelpers
9
+
10
+ def self.call(instruction)
11
+ new(instruction).call
12
+ end
13
+
14
+ def initialize(instruction)
15
+ @instruction = instruction
16
+ end
17
+
18
+ def call
19
+ foreign_key = instruction.extra_args.fetch(:foreign_key, false)
20
+ index = instruction.extra_args.fetch(:index, false)
21
+
22
+ collect_errors(
23
+ assert(
24
+ !foreign_key,
25
+ foreign_key_message,
26
+ ),
27
+ assert(
28
+ !index,
29
+ index_message,
30
+ ),
31
+ )
32
+ end
33
+
34
+ private
35
+
36
+ def index_message
37
+ "Indexing a reference column on creation can make the table unavailable." \
38
+ "Use the `add_index` method in a separate migration to index the column."
39
+ end
40
+
41
+ def foreign_key_message
42
+ "Adding a foreign key constraint must be done in two separate migrations. " \
43
+ "Use the `add_foreign_key` and `validate_foreign_key` methods, or the " \
44
+ "nandi:foreign_key generator, to do this."
45
+ end
46
+
47
+ attr_reader :instruction
48
+ end
49
+ end
50
+ end
@@ -21,6 +21,8 @@ module Nandi
21
21
  RemoveIndexValidator.call(instruction)
22
22
  when :add_column
23
23
  AddColumnValidator.call(instruction)
24
+ when :add_reference
25
+ AddReferenceValidator.call(instruction)
24
26
  else
25
27
  success
26
28
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "nandi/validation/add_column_validator"
4
+ require "nandi/validation/add_reference_validator"
4
5
  require "nandi/validation/remove_index_validator"
5
6
  require "nandi/validation/each_validator"
6
7
  require "nandi/validation/result"
data/lib/nandi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nandi
4
- VERSION = "0.9.0"
4
+ VERSION = "0.11.3"
5
5
  end
@@ -2,5 +2,5 @@ add_column(
2
2
  <%= table %>,
3
3
  <%= name %>,
4
4
  <%= type %>,
5
- <%= extra_args %>
5
+ **<%= extra_args || {} %>
6
6
  )
@@ -1,5 +1,5 @@
1
1
  add_foreign_key(
2
2
  <%= table %>,
3
3
  <%= target %>,
4
- <%= extra_args %>
4
+ **<%= extra_args || {} %>
5
5
  )
@@ -1,5 +1,5 @@
1
1
  add_index(
2
2
  <%= table %>,
3
3
  <%= fields %>,
4
- <%= extra_args %>
4
+ **<%= extra_args || {} %>
5
5
  )
@@ -0,0 +1,5 @@
1
+ add_reference(
2
+ <%= table %>,
3
+ <%= ref_name %>,
4
+ **<%= extra_args || {} %>
5
+ )
@@ -1,5 +1,5 @@
1
1
  remove_column(
2
2
  <%= table %>,
3
3
  <%= name %>,
4
- <%= extra_args %>
4
+ **<%= extra_args || {} %>
5
5
  )
@@ -1,4 +1,4 @@
1
1
  remove_index(
2
2
  <%= table %>,
3
- <%= extra_args %>
3
+ **<%= extra_args || {} %>
4
4
  )
@@ -0,0 +1,5 @@
1
+ remove_reference(
2
+ <%= table %>,
3
+ <%= ref_name %>,
4
+ **<%= extra_args || {} %>
5
+ )
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nandi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.11.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - GoCardless Engineering
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-16 00:00:00.000000000 Z
11
+ date: 2021-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -100,28 +100,28 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 2.3.14
103
+ version: 2.15.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 2.3.14
110
+ version: 2.15.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: pry-byebug
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 3.7.0
117
+ version: 3.9.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 3.7.0
124
+ version: 3.9.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rails
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -140,22 +140,22 @@ dependencies:
140
140
  name: rake
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '12.3'
146
143
  - - ">="
147
144
  - !ruby/object:Gem::Version
148
145
  version: 12.3.3
146
+ - - "~>"
147
+ - !ruby/object:Gem::Version
148
+ version: '13.0'
149
149
  type: :development
150
150
  prerelease: false
151
151
  version_requirements: !ruby/object:Gem::Requirement
152
152
  requirements:
153
- - - "~>"
154
- - !ruby/object:Gem::Version
155
- version: '12.3'
156
153
  - - ">="
157
154
  - !ruby/object:Gem::Version
158
155
  version: 12.3.3
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '13.0'
159
159
  - !ruby/object:Gem::Dependency
160
160
  name: rspec
161
161
  requirement: !ruby/object:Gem::Requirement
@@ -190,14 +190,14 @@ dependencies:
190
190
  requirements:
191
191
  - - "~>"
192
192
  - !ruby/object:Gem::Version
193
- version: '0.61'
193
+ version: '1.0'
194
194
  type: :development
195
195
  prerelease: false
196
196
  version_requirements: !ruby/object:Gem::Requirement
197
197
  requirements:
198
198
  - - "~>"
199
199
  - !ruby/object:Gem::Version
200
- version: '0.61'
200
+ version: '1.0'
201
201
  - !ruby/object:Gem::Dependency
202
202
  name: yard
203
203
  requirement: !ruby/object:Gem::Requirement
@@ -212,7 +212,7 @@ dependencies:
212
212
  - - "~>"
213
213
  - !ruby/object:Gem::Version
214
214
  version: '0.9'
215
- description:
215
+ description:
216
216
  email:
217
217
  - engineering@gocardless.com
218
218
  executables:
@@ -252,6 +252,7 @@ files:
252
252
  - lib/nandi/instructions/add_column.rb
253
253
  - lib/nandi/instructions/add_foreign_key.rb
254
254
  - lib/nandi/instructions/add_index.rb
255
+ - lib/nandi/instructions/add_reference.rb
255
256
  - lib/nandi/instructions/change_column_default.rb
256
257
  - lib/nandi/instructions/create_table.rb
257
258
  - lib/nandi/instructions/drop_constraint.rb
@@ -260,6 +261,7 @@ files:
260
261
  - lib/nandi/instructions/remove_column.rb
261
262
  - lib/nandi/instructions/remove_index.rb
262
263
  - lib/nandi/instructions/remove_not_null_constraint.rb
264
+ - lib/nandi/instructions/remove_reference.rb
263
265
  - lib/nandi/instructions/validate_constraint.rb
264
266
  - lib/nandi/lockfile.rb
265
267
  - lib/nandi/migration.rb
@@ -273,6 +275,7 @@ files:
273
275
  - lib/nandi/timeout_policies/concurrent.rb
274
276
  - lib/nandi/validation.rb
275
277
  - lib/nandi/validation/add_column_validator.rb
278
+ - lib/nandi/validation/add_reference_validator.rb
276
279
  - lib/nandi/validation/each_validator.rb
277
280
  - lib/nandi/validation/failure_helpers.rb
278
281
  - lib/nandi/validation/remove_index_validator.rb
@@ -285,6 +288,7 @@ files:
285
288
  - lib/templates/nandi/renderers/active_record/instructions/add_column/show.rb.erb
286
289
  - lib/templates/nandi/renderers/active_record/instructions/add_foreign_key/show.rb.erb
287
290
  - lib/templates/nandi/renderers/active_record/instructions/add_index/show.rb.erb
291
+ - lib/templates/nandi/renderers/active_record/instructions/add_reference/show.rb.erb
288
292
  - lib/templates/nandi/renderers/active_record/instructions/change_column_default/show.rb.erb
289
293
  - lib/templates/nandi/renderers/active_record/instructions/create_table/show.rb.erb
290
294
  - lib/templates/nandi/renderers/active_record/instructions/drop_constraint/show.rb.erb
@@ -293,12 +297,13 @@ files:
293
297
  - lib/templates/nandi/renderers/active_record/instructions/remove_column/show.rb.erb
294
298
  - lib/templates/nandi/renderers/active_record/instructions/remove_index/show.rb.erb
295
299
  - lib/templates/nandi/renderers/active_record/instructions/remove_not_null_constraint/show.rb.erb
300
+ - lib/templates/nandi/renderers/active_record/instructions/remove_reference/show.rb.erb
296
301
  - lib/templates/nandi/renderers/active_record/instructions/validate_constraint/show.rb.erb
297
302
  homepage: https://github.com/gocardless/nandi
298
303
  licenses:
299
304
  - MIT
300
305
  metadata: {}
301
- post_install_message:
306
+ post_install_message:
302
307
  rdoc_options: []
303
308
  require_paths:
304
309
  - lib
@@ -306,15 +311,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
306
311
  requirements:
307
312
  - - ">="
308
313
  - !ruby/object:Gem::Version
309
- version: '0'
314
+ version: 2.4.0
310
315
  required_rubygems_version: !ruby/object:Gem::Requirement
311
316
  requirements:
312
317
  - - ">="
313
318
  - !ruby/object:Gem::Version
314
319
  version: '0'
315
320
  requirements: []
316
- rubygems_version: 3.0.3
317
- signing_key:
321
+ rubygems_version: 3.2.3
322
+ signing_key:
318
323
  specification_version: 4
319
324
  summary: Fear-free migrations for PostgreSQL
320
325
  test_files: []