nandi 1.0.0 → 2.0.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.
@@ -7,6 +7,7 @@ require "rails/generators"
7
7
  require "nandi/file_diff"
8
8
  require "nandi/file_matcher"
9
9
  require "nandi/lockfile"
10
+ require "nandi/migration_violations"
10
11
 
11
12
  module Nandi
12
13
  class SafeMigrationEnforcer
@@ -16,128 +17,123 @@ module Nandi
16
17
  DEFAULT_AR_MIGRATION_DIR = "db/migrate"
17
18
  DEFAULT_FILE_SPEC = "all"
18
19
 
20
+ attr_reader :violations
21
+
19
22
  def initialize(require_path: nil,
20
23
  safe_migration_dir: DEFAULT_SAFE_MIGRATION_DIR,
21
24
  ar_migration_dir: DEFAULT_AR_MIGRATION_DIR,
22
25
  files: DEFAULT_FILE_SPEC)
23
- @safe_migration_dir = safe_migration_dir
24
- @ar_migration_dir = ar_migration_dir
25
26
  @files = files
26
27
 
27
28
  require require_path unless require_path.nil?
28
29
 
29
- Nandi.configure do |c|
30
- c.migration_directory = @safe_migration_dir
31
- c.output_directory = @ar_migration_dir
32
- end
30
+ configure_legacy_mode_if_needed(safe_migration_dir, ar_migration_dir)
31
+ @violations = MigrationViolations.new
33
32
  end
34
33
 
35
34
  def run
36
- safe_migrations = matching_migrations(@safe_migration_dir)
37
- ar_migrations = matching_migrations(@ar_migration_dir)
35
+ collect_violations
38
36
 
39
- return true if safe_migrations.none? && ar_migrations.none?
40
-
41
- enforce_no_ungenerated_migrations!(safe_migrations, ar_migrations)
42
- enforce_no_hand_written_migrations!(safe_migrations, ar_migrations)
43
- enforce_no_hand_edited_migrations!(ar_migrations)
44
- enforce_no_out_of_date_migrations!(safe_migrations)
37
+ if violations.any?
38
+ raise MigrationLintingFailed, violations.to_error_message
39
+ end
45
40
 
46
41
  true
47
42
  end
48
43
 
49
44
  private
50
45
 
51
- def matching_migrations(dir)
52
- names = Dir.glob(File.join(dir, "*.rb")).map { |path| File.basename(path) }
53
- FileMatcher.call(files: names, spec: @files)
54
- end
55
-
56
- def enforce_no_ungenerated_migrations!(safe_migrations, ar_migrations)
57
- ungenerated_migrations = safe_migrations - ar_migrations
58
- if ungenerated_migrations.any?
59
- error = <<~ERROR
60
- The following migrations are pending generation:
46
+ def configure_legacy_mode_if_needed(safe_dir, ar_dir)
47
+ legacy_mode = safe_dir != DEFAULT_SAFE_MIGRATION_DIR ||
48
+ ar_dir != DEFAULT_AR_MIGRATION_DIR
61
49
 
62
- - #{ungenerated_migrations.sort.join("\n - ")}
50
+ return unless legacy_mode
63
51
 
64
- Please run `rails generate nandi:compile` to generate your migrations.
65
- ERROR
52
+ Nandi.configure do |c|
53
+ c.migration_directory = safe_dir
54
+ c.output_directory = ar_dir
55
+ end
56
+ end
66
57
 
67
- raise MigrationLintingFailed, error
58
+ def collect_violations
59
+ Nandi.config.databases.each do |_, database|
60
+ check_database_violations(database)
68
61
  end
69
62
  end
70
63
 
71
- def enforce_no_hand_written_migrations!(safe_migrations, ar_migrations)
72
- handwritten_migrations = ar_migrations - safe_migrations
73
- handwritten_migration_paths = names_to_paths(handwritten_migrations)
64
+ def check_database_violations(database)
65
+ safe_migrations = matching_migrations(database.migration_directory)
66
+ ar_migrations = matching_migrations(database.output_directory)
74
67
 
75
- if handwritten_migration_paths.any?
76
- error = <<~ERROR
77
- The following migrations have been written by hand, not generated:
68
+ # Check ungenerated migrations and get remaining files
69
+ remaining_safe, remaining_ar = check_ungenerated_migrations(
70
+ safe_migrations, ar_migrations, database
71
+ )
78
72
 
79
- - #{handwritten_migration_paths.sort.join("\n - ")}
73
+ # Check handwritten migrations and get remaining files
74
+ remaining_safe, remaining_ar = check_handwritten_migrations(
75
+ remaining_safe, remaining_ar, database
76
+ )
80
77
 
81
- Please use Nandi to generate your migrations. In exeptional cases, hand-written
82
- ActiveRecord migrations can be added to the .nandiignore file. Doing so will
83
- require additional review that will slow your PR down.
84
- ERROR
78
+ # Check out-of-date migrations
79
+ check_out_of_date_migrations(remaining_safe, database)
85
80
 
86
- raise MigrationLintingFailed, error
87
- end
81
+ # Check hand-edited migrations
82
+ check_hand_edited_migrations(remaining_ar, database)
88
83
  end
89
84
 
90
- def enforce_no_out_of_date_migrations!(safe_migrations)
91
- out_of_date_migrations = safe_migrations.
92
- map { |m| [m, Nandi::Lockfile.get(m)] }.
93
- select do |filename, digests|
94
- Nandi::FileDiff.new(
95
- file_path: File.join(@safe_migration_dir, filename),
96
- known_digest: digests[:source_digest],
97
- ).changed?
98
- end
99
-
100
- if out_of_date_migrations.any?
101
- error = <<~ERROR
102
- The following migrations have changed but not been recompiled:
103
-
104
- - #{out_of_date_migrations.sort.join("\n - ")}
85
+ def check_ungenerated_migrations(safe_migrations, ar_migrations, database)
86
+ missing_files = safe_migrations - ar_migrations
87
+ violations.add_ungenerated(missing_files, database.migration_directory)
105
88
 
106
- Please recompile your migrations to make sure that the changes you expect are
107
- applied.
108
- ERROR
109
-
110
- raise MigrationLintingFailed, error
111
- end
89
+ # Return remaining files after removing processed ones
90
+ [safe_migrations - missing_files, ar_migrations]
112
91
  end
113
92
 
114
- def enforce_no_hand_edited_migrations!(ar_migrations)
115
- hand_altered_migrations = ar_migrations.
116
- map { |m| [m, Nandi::Lockfile.get(m)] }.
117
- select do |filename, digests|
118
- Nandi::FileDiff.new(
119
- file_path: File.join(@ar_migration_dir, filename),
120
- known_digest: digests[:compiled_digest],
121
- ).changed?
122
- end
93
+ def check_handwritten_migrations(safe_migrations, ar_migrations, database)
94
+ handwritten_files = ar_migrations - safe_migrations
95
+ violations.add_handwritten(handwritten_files, database.output_directory)
123
96
 
124
- if hand_altered_migrations.any?
125
- error = <<~ERROR
126
- The following migrations have had their generated content altered:
97
+ # Return remaining files after removing processed ones
98
+ [safe_migrations, ar_migrations - handwritten_files]
99
+ end
127
100
 
128
- - #{hand_altered_migrations.sort.join("\n - ")}
101
+ def check_out_of_date_migrations(safe_migrations, database)
102
+ out_of_date_files = find_changed_files(
103
+ safe_migrations,
104
+ database,
105
+ :source_digest,
106
+ database.migration_directory,
107
+ )
108
+ violations.add_out_of_date(out_of_date_files, database.migration_directory)
109
+ end
129
110
 
130
- Please don't hand-edit generated migrations. If you want to write a regular
131
- ActiveRecord::Migration, please do so and add it to .nandiignore. Note that
132
- this will require additional review that will slow your PR down.
133
- ERROR
111
+ def check_hand_edited_migrations(ar_migrations, database)
112
+ hand_edited_files = find_changed_files(
113
+ ar_migrations,
114
+ database,
115
+ :compiled_digest,
116
+ database.output_directory,
117
+ )
118
+ violations.add_hand_edited(hand_edited_files, database.output_directory)
119
+ end
134
120
 
135
- raise MigrationLintingFailed, error
121
+ def find_changed_files(filenames, database, digest_key, directory)
122
+ filenames.filter_map do |filename|
123
+ digests = Nandi::Lockfile.for(database.name).get(filename)
124
+ file_diff = Nandi::FileDiff.new(
125
+ file_path: File.join(directory, filename),
126
+ known_digest: digests[digest_key],
127
+ )
128
+ filename if file_diff.changed?
136
129
  end
137
130
  end
138
131
 
139
- def names_to_paths(names)
140
- names.map { |name| File.join(@ar_migration_dir, name) }
132
+ def matching_migrations(directory)
133
+ return Set.new unless Dir.exist?(directory)
134
+
135
+ filenames = Dir.glob(File.join(directory, "*.rb")).map { |path| File.basename(path) }
136
+ FileMatcher.call(files: filenames, spec: @files)
141
137
  end
142
138
  end
143
139
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "nandi/validation/add_column_validator"
4
4
  require "nandi/validation/add_reference_validator"
5
+ require "nandi/validation/add_index_validator"
5
6
  require "nandi/validation/remove_index_validator"
6
7
  require "nandi/validation/each_validator"
7
8
  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 = "1.0.0"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/nandi.rb CHANGED
@@ -9,15 +9,16 @@ module Nandi
9
9
  class Error < StandardError; end
10
10
 
11
11
  class << self
12
- def compile(files:)
12
+ def compile(files:, db_name: nil)
13
13
  compiled = files.
14
- map { |f| CompiledMigration.build(f) }
14
+ map { |f| CompiledMigration.build(file_name: f, db_name: db_name) }
15
15
 
16
16
  yield compiled
17
17
  end
18
18
 
19
19
  def configure
20
20
  yield config
21
+ config.validate!
21
22
  end
22
23
 
23
24
  def validator
@@ -27,9 +28,5 @@ module Nandi
27
28
  def config
28
29
  @config ||= Config.new
29
30
  end
30
-
31
- def compiled_output_directory
32
- Nandi.config.output_directory || "db/migrate"
33
- end
34
31
  end
35
32
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nandi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GoCardless Engineering
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-21 00:00:00.000000000 Z
10
+ date: 2025-09-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -120,6 +120,9 @@ files:
120
120
  - lib/nandi/instructions/validate_constraint.rb
121
121
  - lib/nandi/lockfile.rb
122
122
  - lib/nandi/migration.rb
123
+ - lib/nandi/migration_violations.rb
124
+ - lib/nandi/multi_database.rb
125
+ - lib/nandi/multi_db_generator.rb
123
126
  - lib/nandi/renderers.rb
124
127
  - lib/nandi/renderers/active_record.rb
125
128
  - lib/nandi/renderers/active_record/generate.rb