pronto-rails_migrations_annotated 0.0.2 → 0.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2b901020822988af989df4a5c7380a1d0888436b99a1b2ccd835e9b08a88875
4
- data.tar.gz: 51392445670ec4052e0ad6790a6499cf71d62b4d5354a48e27df8ed036ab5f34
3
+ metadata.gz: 88430fc4dc1457f81ee58ddbb791363370cb6e85fcd6ffe17b504717a637d5de
4
+ data.tar.gz: e7f1c20a1f124f27506cb1494d756d1ab314cd9217ce075e68e7a42842380030
5
5
  SHA512:
6
- metadata.gz: 9b50a79d424571f695fe1faeb208ab10d0b474ab161d26c9128cfe355cbaf4de58000cb373daa64b234ac0bd0e2a7b8744ee2dec000f80785c673bdd067db353
7
- data.tar.gz: 4b2b7867aced9e74a06c855337437b802407479d67bb805da19ec502079bdebe748e8f185f383c99338c74d9e9d8194461100c55374b0f1c93bd35a840610143
6
+ metadata.gz: 0f56e324b56c92ef3e2957bd39a6dba969e6fa4748bf8acadc1e077288cf0abb882b6e30b9c7c1f9a6d4d0a73b2be713a344e66c8c2d5aee667e7e6dd379f21d
7
+ data.tar.gz: a9f3ebffd0dd50bc1549d669069cc27a0f3cd67dc0064d428c03f1adffcd0d58df885485e4b9458dbb0c95a8a244e4dc479fbffbb7b2ea169742a73e7b7db5ad
data/.rubocop.yml CHANGED
@@ -1,5 +1,13 @@
1
+ require:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+ - rubocop-performance
5
+
1
6
  AllCops:
2
- TargetRubyVersion: 2.6
7
+ TargetRubyVersion: 2.5
8
+ NewCops: enable
9
+ Exclude:
10
+ - spec/fixtures/somerepo/**/*.rb
3
11
 
4
12
  Style/StringLiterals:
5
13
  Enabled: true
@@ -11,3 +19,13 @@ Style/StringLiteralsInInterpolation:
11
19
 
12
20
  Layout/LineLength:
13
21
  Max: 120
22
+
23
+ Metrics/BlockLength:
24
+ Exclude:
25
+ - spec/**/*.rb
26
+ Metrics/ClassLength:
27
+ Max: 180
28
+ Metrics/AbcSize:
29
+ Max: 25
30
+ Metrics/CyclomaticComplexity:
31
+ Max: 8
data/Gemfile CHANGED
@@ -10,3 +10,6 @@ gem "rake", "~> 13.0"
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
12
  gem "rubocop", "~> 1.21"
13
+ gem "rubocop-performance"
14
+ gem "rubocop-rake"
15
+ gem "rubocop-rspec"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pronto-rails_migrations_annotated (0.0.2)
4
+ pronto-rails_migrations_annotated (0.0.3)
5
5
  pronto (~> 0.11)
6
6
 
7
7
  GEM
@@ -84,6 +84,13 @@ GEM
84
84
  unicode-display_width (>= 1.4.0, < 3.0)
85
85
  rubocop-ast (1.13.0)
86
86
  parser (>= 3.0.1.1)
87
+ rubocop-performance (1.12.0)
88
+ rubocop (>= 1.7.0, < 2.0)
89
+ rubocop-ast (>= 0.4.0)
90
+ rubocop-rake (0.6.0)
91
+ rubocop (~> 1.0)
92
+ rubocop-rspec (2.6.0)
93
+ rubocop (~> 1.19)
87
94
  ruby-progressbar (1.11.0)
88
95
  ruby2_keywords (0.0.5)
89
96
  rugged (1.0.1)
@@ -103,6 +110,9 @@ DEPENDENCIES
103
110
  rake (~> 13.0)
104
111
  rspec (~> 3.0)
105
112
  rubocop (~> 1.21)
113
+ rubocop-performance
114
+ rubocop-rake
115
+ rubocop-rspec
106
116
 
107
117
  BUNDLED WITH
108
118
  2.2.31
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Pronto::RailsMigrationsAnnotated
2
+ [![Gem Version](https://badge.fury.io/rb/pronto-rails_migrations_annotated.svg)](https://badge.fury.io/rb/pronto-rails_migrations_annotated)
2
3
 
3
4
  Pronto runner to enforce migrations to be in separate PRs, but allow schema annotations.
4
5
  Also check for obvious things like adding migration number that is not present in PR to structure.sql.
@@ -8,17 +9,13 @@ Also check for obvious things like adding migration number that is not present i
8
9
  Add this line to your application's Gemfile:
9
10
 
10
11
  ```ruby
11
- gem 'pronto-rails_migrations_annotated'
12
+ gem 'pronto-rails_migrations_annotated', require: false
12
13
  ```
13
14
 
14
15
  And then execute:
15
16
 
16
17
  $ bundle install
17
18
 
18
- Or install it yourself as:
19
-
20
- $ gem install pronto-rails_migrations_annotated
21
-
22
19
  ## Usage
23
20
 
24
21
  When in gemfile - pronto should pick up this runner automatically.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Pronto
4
4
  module RailsMigrationsAnnotatedVersion
5
- VERSION = "0.0.2"
5
+ VERSION = "0.0.3"
6
6
  end
7
7
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pronto'
3
+ require "pronto"
4
4
  require_relative "rails_migrations_annotated/version"
5
5
 
6
6
  module Pronto
7
+ # Runner that detects migration smells
7
8
  class RailsMigrationsAnnotated < Runner
8
9
  VERSION = Pronto::RailsMigrationsAnnotatedVersion::VERSION
9
10
 
@@ -11,13 +12,15 @@ module Pronto
11
12
  @messages = []
12
13
 
13
14
  check_migrations_mixed_with_code
14
- check_migration_version_numbers
15
+ check_migration_schema_version
16
+ check_structure_migration_version_numbers
15
17
  check_large_schema_diff
18
+ check_large_structure_diff
16
19
 
17
20
  @messages
18
21
  end
19
22
 
20
- # todo: override def self.title ?
23
+ # TODO: override def self.title ?
21
24
 
22
25
  private
23
26
 
@@ -25,19 +28,11 @@ module Pronto
25
28
  patches = [patches] unless patches.is_a?(Array)
26
29
  patches.each do |patch|
27
30
  target_line = case line
28
- when :first then patch.added_lines.first || patch.lines.first
29
- when :last then patch.added_lines.last || patch.lines.last
30
- else line
31
- end
32
-
33
- @messages << Message.new(
34
- patch.delta.new_file[:path],
35
- target_line,
36
- level,
37
- message,
38
- nil,
39
- self.class
40
- )
31
+ when :first then patch.added_lines.first || patch.lines.first
32
+ when :last then patch.added_lines.last || patch.lines.last
33
+ else line
34
+ end
35
+ @messages << Message.new(patch.delta.new_file[:path], target_line, level, message, nil, self.class)
41
36
  end
42
37
  end
43
38
 
@@ -48,14 +43,18 @@ module Pronto
48
43
 
49
44
  def check_large_schema_diff
50
45
  return unless diff_threshold
46
+ return unless schema_patches.sum(&:additions) >= diff_threshold ||
47
+ schema_patches.sum(&:deletions) >= diff_threshold
51
48
 
52
- if schema_patches.sum(&:additions) >= diff_threshold || schema_patches.sum(&:deletions) >= diff_threshold
53
- add_message_at_patch(schema_patches.first, "Large schema diff, pay attention")
54
- end
49
+ add_message_at_patch(schema_patches.first, "Large schema diff, pay attention")
50
+ end
55
51
 
56
- if structure_patches.sum(&:additions) >= diff_threshold || structure_patches.sum(&:deletions) >= diff_threshold
57
- add_message_at_patch(structure_patches.first, "Large structure diff, pay attention")
58
- end
52
+ def check_large_structure_diff
53
+ return unless diff_threshold
54
+ return unless structure_patches.sum(&:additions) >= diff_threshold ||
55
+ structure_patches.sum(&:deletions) >= diff_threshold
56
+
57
+ add_message_at_patch(structure_patches.first, "Large structure diff, pay attention")
59
58
  end
60
59
 
61
60
  def check_migrations_mixed_with_code
@@ -64,82 +63,109 @@ module Pronto
64
63
  add_message_at_patch(migration_patches, "Do not mix migrations with other stuff", :fatal)
65
64
  end
66
65
 
67
- def check_migration_version_numbers
68
- return unless migration_patches.any?
66
+ def check_migration_schema_version
67
+ return if migration_patches.none? || !File.exist?(schema_file_name) || gitignored?(schema_file_name)
69
68
 
70
- version_numbers = migration_patches.map { |patch| patch.delta.new_file[:path].sub(/^[^0-9]*([0-9]+).+/, '\1') }
71
-
72
- schema_file_name = 'db/schema.rb'
73
- if File.exist?(schema_file_name) && !gitignored?(schema_file_name)
74
- if schema_patches.none?
75
- add_message_at_patch(migration_patches, "Migration file detected, but no changes in schema.rb", :error)
76
- else
77
- match = File.read(schema_file_name).match(%r{ActiveRecord::Schema.define\(version:\s*(?<version>[0-9_]+)\s*\)})
78
- if match
79
- schema_migration_version = match[:version]
80
- schema_migration_version_clean = schema_migration_version.gsub(/[^0-9]/, '')
81
- version_numbers.select { |version| version > schema_migration_version_clean }.each do |wrong_version|
82
- add_message_at_patch(
83
- migration_patches.first { |patch| patch.delta.new_file[:path].include?(wrong_version) },
84
- "Migration version #{wrong_version} is above schema.rb version #{schema_migration_version}"
85
- )
86
- end
87
- else
88
- add_message_at_patch(migration_patches.first, "Cannot detect schema migration version", :warning)
89
- end
90
- end
69
+ if schema_patches.none?
70
+ return add_message_at_patch(migration_patches, "Migration file detected, but no changes in schema.rb", :error)
91
71
  end
92
72
 
93
- structure_file_name = 'db/structure.sql'
94
- if File.exist?(structure_file_name) && !gitignored?(structure_file_name)
95
- migration_line_regex = /\A\s*\('(?<version>[0-9]{14})'\)/
96
- structure_file_lines = File.readlines(structure_file_name)
97
- versions_from_schema = structure_file_lines.select{|line| line =~ migration_line_regex }
98
-
99
- missing_from_structure = false
100
- version_numbers.select { |version| versions_from_schema.none? { |strct_ver| strct_ver.include?(version) } }
101
- .each do |wrong_version|
102
- missing_from_structure = true
103
- add_message_at_patch(
104
- migration_patches.first { |patch| patch.delta.new_file[:path].include?(wrong_version) },
105
- "Migration #{wrong_version} is missing from structure.sql", :error
106
- )
107
- end
73
+ migration_version_numbers.select { |version| schema_migration_version&.<(version) }.each do |wrong_version|
74
+ add_message_at_patch(
75
+ migration_patches.first { |patch| patch.delta.new_file[:path].include?(wrong_version) },
76
+ "Migration version #{wrong_version} is above schema.rb version #{schema_migration_version}"
77
+ )
78
+ end
79
+ end
108
80
 
109
- if structure_patches.none? && !missing_from_structure
110
- add_message_at_patch(migration_patches, "Migration file detected, but no changes in structure.sql")
111
- end
81
+ def schema_migration_version
82
+ return @schema_migration_version if defined? @schema_migration_version
112
83
 
113
- structure_patches.each do |patch|
114
- patch.added_lines.each do |line|
115
- next unless line.content.match?(migration_line_regex)
116
- match = line.content.match(migration_line_regex)
117
- version = match[:version]
118
- next if version_numbers.include?(version) ||
119
- line.content.end_with?(',') && patch.deleted_lines.any? { |deleted| deleted.content.include?(version) }
84
+ @schema_migration_version ||= File.read(schema_file_name)
85
+ .match(/ActiveRecord::Schema.define\(version:\s*(?<version>[0-9_]+)\s*\)/)
86
+ &.[](:version)&.gsub(/[^0-9]/, "") ||
87
+ (add_message_at_patch(migration_patches.first,
88
+ "Cannot detect migration version in schema.rb. "\
89
+ "Migration versions are not checked.",
90
+ :warning) && nil)
91
+ end
120
92
 
121
- add_message_at_patch(patch, "Migration #{version} is not present in this changeset", :error, line: line)
122
- end
123
- end
93
+ def migration_version_numbers
94
+ @migration_version_numbers ||= migration_patches.map do |patch|
95
+ patch.delta.new_file[:path].sub(/^[^0-9]*([0-9]+).+/, '\1')
96
+ end
97
+ end
124
98
 
125
- bad_semicolon = !versions_from_schema.last.end_with?("');\n")
126
- unsorted_migrations = versions_from_schema != versions_from_schema.sort
99
+ def check_structure_migration_version_numbers
100
+ return unless migration_patches.any?
127
101
 
128
- if bad_semicolon || unsorted_migrations
129
- add_message_at_patch(
130
- structure_patches.first{ |patch| patch.lines.any?{ |line| line.content.match?(migration_line_regex) } },
131
- "Migration versions must be sorted and have correct syntax"
132
- )
133
- end
102
+ structure_file_name = "db/structure.sql"
103
+ return if !File.exist?(structure_file_name) || gitignored?(structure_file_name)
104
+
105
+ structure_file_lines = File.readlines(structure_file_name)
106
+ versions_from_schema = structure_file_lines.grep(migration_line_regex)
107
+
108
+ check_structure_versions_present(versions_from_schema)
109
+ check_structure_migration_missing
110
+
111
+ check_structure_versions_syntax(versions_from_schema)
112
+ check_structure_versions_sorted(versions_from_schema)
113
+ check_structure_ending(structure_file_lines)
114
+ end
115
+
116
+ def check_structure_versions_present(versions_from_schema)
117
+ found_missing_migration = false
118
+ migration_version_numbers.select { |version| versions_from_schema.none? { |ver| ver.include?(version) } }
119
+ .each do |wrong_version|
120
+ found_missing_migration = true
121
+ add_message_at_patch(migration_patches.first { |patch| patch.delta.new_file[:path].include?(wrong_version) },
122
+ "Migration #{wrong_version} is missing from structure.sql", :error)
123
+ end
124
+
125
+ return if structure_patches.any? || found_missing_migration
126
+
127
+ add_message_at_patch(migration_patches, "Migration file detected, but no changes in structure.sql")
128
+ end
134
129
 
135
- if structure_patches.any? &&
136
- !(structure_file_lines.last(2) == ["\n", "\n"] && structure_file_lines[-3] != "\n") &&
137
- !structure_file_lines.last.match?(/\A\s*[^\s]+\s*\n/)
138
- add_message_at_patch(structure_patches.last,
139
- "structure.sql must end with a newline or 2 empty lines", line: :last)
130
+ def check_structure_migration_missing
131
+ structure_patches.each do |patch|
132
+ patch.added_lines.select { |line| line.content.match?(migration_line_regex) }.each do |line|
133
+ version = line.content.match(migration_line_regex)[:version]
134
+ next if migration_version_numbers.include?(version)
135
+ next if line.content.end_with?(",") &&
136
+ patch.deleted_lines.any? { |del| del.content.include?("#{version}');") }
137
+
138
+ add_message_at_patch(patch, "Migration #{version} is not present in this changeset", :error, line: line)
140
139
  end
141
140
  end
141
+ end
142
+
143
+ def check_structure_versions_sorted(versions_from_schema)
144
+ return if versions_from_schema == versions_from_schema.sort
145
+
146
+ add_message_at_patch(
147
+ structure_patches.first { |patch| patch.lines.any? { |line| line.content.match?(migration_line_regex) } },
148
+ "Migration versions must be sorted and have correct syntax"
149
+ )
150
+ end
151
+
152
+ def check_structure_versions_syntax(versions_from_schema)
153
+ return if versions_from_schema.last.end_with?("');\n") &&
154
+ versions_from_schema.all? { |line| line.end_with?("'),\n", "');\n") }
142
155
 
156
+ add_message_at_patch(
157
+ structure_patches.first { |patch| patch.lines.any? { |line| line.content.match?(migration_line_regex) } },
158
+ "Migration version lines must be separated by comma and end with semicolon"
159
+ )
160
+ end
161
+
162
+ def check_structure_ending(structure_file_lines)
163
+ return if structure_patches.none?
164
+ return if structure_file_lines.last(2) == %W[\n \n] && structure_file_lines[-3] != "\n"
165
+ return if structure_file_lines.last.match?(/\A\s*[^\s]+\s*\n/)
166
+
167
+ add_message_at_patch(structure_patches.last,
168
+ "structure.sql must end with a newline or 2 empty lines", line: :last)
143
169
  end
144
170
 
145
171
  def gitignored?(path)
@@ -149,6 +175,22 @@ module Pronto
149
175
  nil
150
176
  end
151
177
 
178
+ def schema_file_name
179
+ "db/schema.rb"
180
+ end
181
+
182
+ def migration_line_regex
183
+ /\A\s*\('(?<version>[0-9]{14})'\)/
184
+ end
185
+
186
+ def migration_related_files
187
+ Regexp.union(
188
+ %r{db/migrate/.*[0-9]+_\w+.rb},
189
+ %r{db/schema.rb},
190
+ %r{db/structure.sql}
191
+ )
192
+ end
193
+
152
194
  def migration_patches
153
195
  # nb: there may be engines added to migrations_paths in config or database.yml
154
196
  # but cannot check for this without more knowledge into the particular app
@@ -159,14 +201,14 @@ module Pronto
159
201
  end
160
202
 
161
203
  def non_migration_related_patches
162
- @patches.reject do |patch|
204
+ @patches.select do |patch|
163
205
  path = patch.delta.new_file[:path]
164
- # lines = added_lines + deleted_lines, allow edits in comments
165
- path =~ %r{db/migrate/.*[0-9]+_\w+.rb} || path =~ %r{db/schema.rb} || path =~ %r{db/structure.sql} ||
166
- path.end_with?('.rb') && patch.lines.all? do |line|
167
- !(line.addition? || line.deletion?) ||
168
- (line.content =~ /\A\s*#/ || line.content =~ /\A\s*\z/)
169
- end
206
+ next false if path.match?(migration_related_files)
207
+
208
+ # allow edits in comments and blank lines (model annotations usually come there)
209
+ !path.end_with?(".rb") || patch.lines.any? do |line|
210
+ (line.addition? || line.deletion?) && !line.content.match?(/\A\s*(#|\z)/)
211
+ end
170
212
  end
171
213
  end
172
214
 
@@ -177,6 +219,5 @@ module Pronto
177
219
  def structure_patches
178
220
  @structure_patches = @patches.select { |patch| patch.delta.new_file[:path] =~ %r{db/structure.sql} }
179
221
  end
180
-
181
222
  end
182
223
  end
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  TEXT
16
16
  spec.homepage = "https://github.com/Vasfed/pronto-rails_migrations_annotated"
17
17
  spec.license = "MIT"
18
- spec.required_ruby_version = ">= 2.3.0"
18
+ spec.required_ruby_version = ">= 2.5.0"
19
19
 
20
20
  spec.metadata["homepage_uri"] = spec.homepage
21
21
  spec.metadata["source_code_uri"] = "https://github.com/Vasfed/pronto-rails_migrations_annotated"
@@ -30,5 +30,5 @@ Gem::Specification.new do |spec|
30
30
  end
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_dependency "pronto", '~>0.11'
33
+ spec.add_dependency "pronto", "~>0.11"
34
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pronto-rails_migrations_annotated
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vasily Fedoseyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-13 00:00:00.000000000 Z
11
+ date: 2021-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pronto
@@ -57,7 +57,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 2.3.0
60
+ version: 2.5.0
61
61
  required_rubygems_version: !ruby/object:Gem::Requirement
62
62
  requirements:
63
63
  - - ">="