nandi 0.8.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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +483 -0
  3. data/Rakefile +8 -0
  4. data/exe/nandi-enforce +36 -0
  5. data/lib/generators/nandi/check_constraint/USAGE +19 -0
  6. data/lib/generators/nandi/check_constraint/check_constraint_generator.rb +52 -0
  7. data/lib/generators/nandi/check_constraint/templates/add_check_constraint.rb +15 -0
  8. data/lib/generators/nandi/check_constraint/templates/validate_check_constraint.rb +9 -0
  9. data/lib/generators/nandi/compile/USAGE +19 -0
  10. data/lib/generators/nandi/compile/compile_generator.rb +62 -0
  11. data/lib/generators/nandi/foreign_key/USAGE +47 -0
  12. data/lib/generators/nandi/foreign_key/foreign_key_generator.rb +91 -0
  13. data/lib/generators/nandi/foreign_key/templates/add_foreign_key.rb +13 -0
  14. data/lib/generators/nandi/foreign_key/templates/add_reference.rb +11 -0
  15. data/lib/generators/nandi/foreign_key/templates/validate_foreign_key.rb +9 -0
  16. data/lib/generators/nandi/migration/USAGE +9 -0
  17. data/lib/generators/nandi/migration/migration_generator.rb +24 -0
  18. data/lib/generators/nandi/migration/templates/migration.rb +13 -0
  19. data/lib/generators/nandi/not_null_check/USAGE +19 -0
  20. data/lib/generators/nandi/not_null_check/not_null_check_generator.rb +56 -0
  21. data/lib/generators/nandi/not_null_check/templates/add_not_null_check.rb +11 -0
  22. data/lib/generators/nandi/not_null_check/templates/validate_not_null_check.rb +9 -0
  23. data/lib/nandi.rb +35 -0
  24. data/lib/nandi/compiled_migration.rb +86 -0
  25. data/lib/nandi/config.rb +126 -0
  26. data/lib/nandi/file_diff.rb +32 -0
  27. data/lib/nandi/file_matcher.rb +72 -0
  28. data/lib/nandi/formatting.rb +79 -0
  29. data/lib/nandi/instructions.rb +21 -0
  30. data/lib/nandi/instructions/add_check_constraint.rb +23 -0
  31. data/lib/nandi/instructions/add_column.rb +24 -0
  32. data/lib/nandi/instructions/add_foreign_key.rb +40 -0
  33. data/lib/nandi/instructions/add_index.rb +50 -0
  34. data/lib/nandi/instructions/add_reference.rb +23 -0
  35. data/lib/nandi/instructions/change_column_default.rb +23 -0
  36. data/lib/nandi/instructions/create_table.rb +83 -0
  37. data/lib/nandi/instructions/drop_constraint.rb +22 -0
  38. data/lib/nandi/instructions/drop_table.rb +21 -0
  39. data/lib/nandi/instructions/irreversible_migration.rb +15 -0
  40. data/lib/nandi/instructions/remove_column.rb +23 -0
  41. data/lib/nandi/instructions/remove_index.rb +41 -0
  42. data/lib/nandi/instructions/remove_not_null_constraint.rb +22 -0
  43. data/lib/nandi/instructions/remove_reference.rb +23 -0
  44. data/lib/nandi/instructions/validate_constraint.rb +22 -0
  45. data/lib/nandi/lockfile.rb +58 -0
  46. data/lib/nandi/migration.rb +388 -0
  47. data/lib/nandi/renderers.rb +7 -0
  48. data/lib/nandi/renderers/active_record.rb +13 -0
  49. data/lib/nandi/renderers/active_record/generate.rb +59 -0
  50. data/lib/nandi/renderers/active_record/instructions.rb +146 -0
  51. data/lib/nandi/safe_migration_enforcer.rb +143 -0
  52. data/lib/nandi/timeout_policies.rb +38 -0
  53. data/lib/nandi/timeout_policies/access_exclusive.rb +54 -0
  54. data/lib/nandi/timeout_policies/concurrent.rb +64 -0
  55. data/lib/nandi/validation.rb +11 -0
  56. data/lib/nandi/validation/add_column_validator.rb +43 -0
  57. data/lib/nandi/validation/add_reference_validator.rb +38 -0
  58. data/lib/nandi/validation/each_validator.rb +34 -0
  59. data/lib/nandi/validation/failure_helpers.rb +35 -0
  60. data/lib/nandi/validation/remove_index_validator.rb +30 -0
  61. data/lib/nandi/validation/result.rb +30 -0
  62. data/lib/nandi/validation/timeout_validator.rb +37 -0
  63. data/lib/nandi/validator.rb +102 -0
  64. data/lib/templates/nandi/renderers/active_record/generate/show.rb.erb +27 -0
  65. data/lib/templates/nandi/renderers/active_record/instructions/add_check_constraint/show.rb.erb +7 -0
  66. data/lib/templates/nandi/renderers/active_record/instructions/add_column/show.rb.erb +6 -0
  67. data/lib/templates/nandi/renderers/active_record/instructions/add_foreign_key/show.rb.erb +5 -0
  68. data/lib/templates/nandi/renderers/active_record/instructions/add_index/show.rb.erb +5 -0
  69. data/lib/templates/nandi/renderers/active_record/instructions/add_reference/show.rb.erb +5 -0
  70. data/lib/templates/nandi/renderers/active_record/instructions/change_column_default/show.rb.erb +1 -0
  71. data/lib/templates/nandi/renderers/active_record/instructions/create_table/show.rb.erb +8 -0
  72. data/lib/templates/nandi/renderers/active_record/instructions/drop_constraint/show.rb.erb +4 -0
  73. data/lib/templates/nandi/renderers/active_record/instructions/drop_table/show.rb.erb +1 -0
  74. data/lib/templates/nandi/renderers/active_record/instructions/irreversible_migration/show.rb.erb +1 -0
  75. data/lib/templates/nandi/renderers/active_record/instructions/remove_column/show.rb.erb +5 -0
  76. data/lib/templates/nandi/renderers/active_record/instructions/remove_index/show.rb.erb +4 -0
  77. data/lib/templates/nandi/renderers/active_record/instructions/remove_not_null_constraint/show.rb.erb +1 -0
  78. data/lib/templates/nandi/renderers/active_record/instructions/remove_reference/show.rb.erb +5 -0
  79. data/lib/templates/nandi/renderers/active_record/instructions/validate_constraint/show.rb.erb +3 -0
  80. metadata +317 -0
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/exe/nandi-enforce ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require "bundler"
6
+ Bundler.setup(:default)
7
+
8
+ require "optparse"
9
+
10
+ require_relative "../lib/nandi"
11
+ require_relative "../lib/nandi/safe_migration_enforcer"
12
+
13
+ opts = {}
14
+ OptionParser.new do |o|
15
+ o.on("--safe-migration-dir DIR", "directory containing Nandi migrations") do |v|
16
+ opts[:safe_migration_dir] = v
17
+ end
18
+ o.on("--ar-migration-dir DIR", "directory containing ActiveRecord migrations") do |v|
19
+ opts[:ar_migration_dir] = v
20
+ end
21
+ o.on("--require PATH", "file to require before execution") do |v|
22
+ opts[:require_path] = v
23
+ end
24
+ o.on("--files FILE_SPEC",
25
+ "Files to compile for check - eg, all, git-diff, 201901010101") do |v|
26
+ opts[:files] = v
27
+ end
28
+ o.on("-h", "--help") do
29
+ puts o
30
+ exit
31
+ end
32
+ end.parse!
33
+
34
+ enforcer = Nandi::SafeMigrationEnforcer.new(opts)
35
+
36
+ enforcer.run
@@ -0,0 +1,19 @@
1
+ Description:
2
+ Generates two new database migrations which will safely add an arbitrary
3
+ check constraint and validate it separately.
4
+
5
+ Example:
6
+ rails generate nandi:check_constraint foos baz_or_quux_not_null
7
+
8
+ This will create:
9
+ db/safe_migrations/20190424123727_add_check_constraint_baz_or_quux_not_null_on_foos.rb
10
+ db/safe_migrations/20190424123728_validate_check_constraint_baz_or_quux_not_null_on_foos.rb
11
+
12
+ Example:
13
+ rails generate nandi:check_constraint foos baz_or_quux_not_null --validation-timeout 20000
14
+
15
+ This will create:
16
+ db/safe_migrations/20190424123727_add_check_constraint_baz_or_quux_not_null_on_foos.rb
17
+ db/safe_migrations/20190424123728_validate_check_constraint_baz_or_quux_not_null_on_foos.rb
18
+
19
+ The statement timeout in the second migration will be set to 20,000ms.
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "nandi/formatting"
5
+
6
+ module Nandi
7
+ class CheckConstraintGenerator < Rails::Generators::Base
8
+ include Nandi::Formatting
9
+
10
+ argument :table, type: :string
11
+ argument :name, type: :string
12
+ class_option :validation_timeout, type: :numeric, default: 15 * 60 * 1000
13
+
14
+ source_root File.expand_path("templates", __dir__)
15
+
16
+ attr_reader :add_check_constraint_name, :validate_check_constraint_name
17
+
18
+ def add_check_constraint
19
+ self.table = table.to_sym
20
+ self.name = name.to_sym
21
+
22
+ @add_check_constraint_name = "add_check_constraint_#{name}_on_#{table}"
23
+
24
+ template(
25
+ "add_check_constraint.rb",
26
+ "#{base_path}/#{timestamp}_#{add_check_constraint_name}.rb",
27
+ )
28
+ end
29
+
30
+ def validate_check_constraint
31
+ self.table = table.to_sym
32
+ self.name = name.to_sym
33
+
34
+ @validate_check_constraint_name = "validate_check_constraint_#{name}_on_#{table}"
35
+
36
+ template(
37
+ "validate_check_constraint.rb",
38
+ "#{base_path}/#{timestamp(1)}_#{validate_check_constraint_name}.rb",
39
+ )
40
+ end
41
+
42
+ private
43
+
44
+ def base_path
45
+ Nandi.config.migration_directory || "db/safe_migrations"
46
+ end
47
+
48
+ def timestamp(offset = 0)
49
+ (Time.now.utc + offset).strftime("%Y%m%d%H%M%S")
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= add_check_constraint_name.camelize %> < Nandi::Migration
4
+ def up
5
+ add_check_constraint <%= format_value(table) %>,
6
+ <%= format_value(name) %>,
7
+ <<~SQL
8
+ -- foo IS NOT NULL OR bar IS NOT NULL
9
+ SQL
10
+ end
11
+
12
+ def down
13
+ drop_constraint <%= format_value(table) %>, <%= format_value(name) %>
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= validate_check_constraint_name.camelize %> < Nandi::Migration
4
+ def up
5
+ validate_constraint <%= format_value(table) %>, <%= format_value(name) %>
6
+ end
7
+
8
+ def down; end
9
+ end
@@ -0,0 +1,19 @@
1
+ Description:
2
+ Takes all files from the db/nandi directory and turns
3
+ them into ActiveRecordmigration files.
4
+
5
+ Examples:
6
+ rails generate nandi:compile
7
+ compiles all migrations that have changed since last git commit
8
+
9
+ rails generate nandi:compile --files all
10
+ compiles all migrations ever
11
+
12
+ rails generate nandi:compile --files 20190101010101
13
+ compiles specific migration
14
+
15
+ rails generate nandi:compile --files 20190101
16
+ compiles all migrations from January 1st 2019
17
+
18
+ rails generate nandi:compile --files >=20190101
19
+ compiles all migrations from January 1st 2019 and after
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "nandi"
5
+ require "nandi/migration"
6
+ require "nandi/file_matcher"
7
+ require "nandi/lockfile"
8
+
9
+ module Nandi
10
+ class CompileGenerator < Rails::Generators::Base
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ class_option :files,
14
+ type: :string,
15
+ default: Nandi.config.compile_files,
16
+ desc: <<-DESC
17
+ Files to compile. May be one of the following:
18
+ -- 'all' compiles all files
19
+ -- 'git-diff' only changed
20
+ -- a full or partial version timestamp, eg '20190101010101', '20190101'
21
+ -- a timestamp range , eg '>=20190101010101'
22
+ DESC
23
+
24
+ def compile_migration_files
25
+ Nandi.compile(files: files) do |results|
26
+ results.each do |result|
27
+ Nandi::Lockfile.add(
28
+ file_name: result.file_name,
29
+ source_digest: result.source_digest,
30
+ compiled_digest: result.compiled_digest,
31
+ )
32
+
33
+ unless result.migration_unchanged?
34
+ create_file result.output_path, result.body, force: true
35
+ end
36
+ end
37
+ end
38
+
39
+ Nandi::Lockfile.persist!
40
+ end
41
+
42
+ private
43
+
44
+ def safe_migrations_dir
45
+ if Nandi.config.migration_directory.nil?
46
+ Rails.root.join("db", "safe_migrations").to_s
47
+ else
48
+ File.expand_path(Nandi.config.migration_directory)
49
+ end
50
+ end
51
+
52
+ def output_path
53
+ Nandi.config.output_directory || "db/migrate"
54
+ end
55
+
56
+ def files
57
+ safe_migration_files = Dir.chdir(safe_migrations_dir) { Dir["*.rb"] }
58
+ FileMatcher.call(files: safe_migration_files, spec: options["files"]).
59
+ map { |file| File.join(safe_migrations_dir, file) }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,47 @@
1
+ Description:
2
+ Generates two new database migrations which will safely add a foreign key
3
+ constraint and validate it separately.
4
+
5
+ Example:
6
+ rails generate nandi:foreign_key foos bars
7
+
8
+ This will create:
9
+ db/safe_migrations/20190424123727_add_reference_on_bars_to_foos.rb
10
+ db/safe_migrations/20190424123728_add_foreign_key_on_bars_to_foos.rb
11
+ db/safe_migrations/20190424123729_validate_foreign_key_on_bars_to_foos.rb
12
+
13
+ These migrations will create a new column on the table foos called bar_id, a
14
+ new foreign key constraint on that column against the id column of bars, and
15
+ validate that constraint separately.
16
+
17
+ Example:
18
+ rails generate nandi:foreign_key foos bars --type text
19
+
20
+ This will create:
21
+ db/safe_migrations/20190424123727_add_reference_on_bars_to_foos.rb
22
+ db/safe_migrations/20190424123728_add_foreign_key_on_bars_to_foos.rb
23
+ db/safe_migrations/20190424123729_validate_foreign_key_on_bars_to_foos.rb
24
+
25
+ These migrations will create a new text column on the table foos called bar_id,
26
+ a new foreign key constraint on that column against the id column of bars, and
27
+ validate that constraint separately.
28
+
29
+ Example:
30
+ rails generate nandi:foreign_key foos bars --no-create-column
31
+
32
+ This will create:
33
+ db/safe_migrations/20190424123727_add_foreign_key_on_bars_to_foos.rb
34
+ db/safe_migrations/20190424123728_validate_foreign_key_on_bars_to_foos.rb
35
+
36
+ Assumes that there is a column on table foos called bar_id that points to bars.
37
+ Will create an FK constraint called foos_bars_fk.
38
+
39
+ Example:
40
+ rails generate nandi:foreign_key foos bars --column special_bar_id --name my_fk
41
+
42
+ This will create:
43
+ db/safe_migrations/20190424123727_add_foreign_key_on_bars_to_foos.rb
44
+ db/safe_migrations/20190424123728_validate_foreign_key_on_bars_to_foos.rb
45
+
46
+ Assumes that there is a column on table foos called special_bar_id that points to
47
+ bars. Will create an FK constraint called my_fk
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "nandi/formatting"
5
+
6
+ module Nandi
7
+ class ForeignKeyGenerator < Rails::Generators::Base
8
+ include Nandi::Formatting
9
+
10
+ argument :table, type: :string
11
+ argument :target, type: :string
12
+ class_option :name, type: :string
13
+ class_option :column, type: :string
14
+ class_option :type, type: :string
15
+ class_option :no_create_column, type: :boolean
16
+ class_option :validation_timeout, type: :numeric, default: 15 * 60 * 1000
17
+
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ attr_reader :add_reference_name,
21
+ :add_foreign_key_name,
22
+ :validate_foreign_key_name
23
+
24
+ def add_reference
25
+ return if options["no_create_column"]
26
+
27
+ self.table = table.to_sym
28
+
29
+ @add_reference_name = "add_reference_on_#{table}_to_#{target}"
30
+
31
+ template(
32
+ "add_reference.rb",
33
+ "#{base_path}/#{timestamp}_#{add_reference_name}.rb",
34
+ )
35
+ end
36
+
37
+ def add_foreign_key
38
+ self.table = table.to_sym
39
+ self.target = target.to_sym
40
+
41
+ @add_foreign_key_name = "add_foreign_key_on_#{table}_to_#{target}"
42
+
43
+ template(
44
+ "add_foreign_key.rb",
45
+ "#{base_path}/#{timestamp(1)}_#{add_foreign_key_name}.rb",
46
+ )
47
+ end
48
+
49
+ def validate_foreign_key
50
+ self.table = table.to_sym
51
+ self.target = target.to_sym
52
+
53
+ @validate_foreign_key_name = "validate_foreign_key_on_#{table}_to_#{target}"
54
+
55
+ template(
56
+ "validate_foreign_key.rb",
57
+ "#{base_path}/#{timestamp(2)}_#{validate_foreign_key_name}.rb",
58
+ )
59
+ end
60
+
61
+ private
62
+
63
+ def type
64
+ options["type"]&.to_sym
65
+ end
66
+
67
+ def reference_name
68
+ target.singularize.to_sym
69
+ end
70
+
71
+ def base_path
72
+ Nandi.config.migration_directory || "db/safe_migrations"
73
+ end
74
+
75
+ def timestamp(offset = 0)
76
+ (Time.now.utc + offset).strftime("%Y%m%d%H%M%S")
77
+ end
78
+
79
+ def name
80
+ options["name"]&.to_sym || :"#{@table}_#{@target}_fk"
81
+ end
82
+
83
+ def column
84
+ options["column"]&.to_sym
85
+ end
86
+
87
+ def any_options?
88
+ options["name"] || options["column"]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= add_foreign_key_name.camelize %> < Nandi::Migration
4
+ def up
5
+ add_foreign_key <%= format_value(table) %>, <%= format_value(target) %><% if any_options? %>,
6
+ <% if column %>column: <%= format_value(column) %><% end %><% if column && name %>,<% end %>
7
+ <% if name %>name: <%= format_value(name) %><% end %> <% end %>
8
+ end
9
+
10
+ def down
11
+ drop_constraint <%= format_value(table) %>, <%= format_value(name) %>
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= add_reference_name.camelize %> < Nandi::Migration
4
+ def up
5
+ add_reference <%= format_value(table) %>, <%= format_value(reference_name) %><% if type %>, type: <%= format_value(type) %><% end %>
6
+ end
7
+
8
+ def down
9
+ remove_reference <%= format_value(table) %>, <%= format_value(reference_name) %>
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= validate_foreign_key_name.camelize %> < Nandi::Migration
4
+ def up
5
+ validate_constraint <%= format_value(@table) %>, <%= format_value(name) %>
6
+ end
7
+
8
+ def down; end
9
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Generates a new database migration which uses the
3
+ nandi DSL in db/nandi
4
+
5
+ Example:
6
+ rails generate nandi:migration add_stuff_to_things
7
+
8
+ This will create:
9
+ db/safe_migrations/20190424123727_add_stuff_to_things.rb
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Nandi
6
+ class MigrationGenerator < Rails::Generators::NamedBase
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ def create_migration_file
10
+ timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
11
+
12
+ template(
13
+ "migration.rb",
14
+ "#{base_path}/#{timestamp}_#{file_name.underscore}.rb",
15
+ )
16
+ end
17
+
18
+ private
19
+
20
+ def base_path
21
+ Nandi.config.migration_directory || "db/safe_migrations"
22
+ end
23
+ end
24
+ end