rubocop-migration 0.8.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0813a8046835fbb1c8382152971eaada39e144aabb2266cda2115b2e5c2a1398'
4
- data.tar.gz: 38580189a800f01224c4dbfd39d7aed4707509d148ec1deed74e936858a99624
3
+ metadata.gz: '04080713782c1efcf724f467e77e9e550d70070bcb1d69592caeb1d4740b9282'
4
+ data.tar.gz: 80fece0029a643072003348334363a51a84c636fdcf2c50219a3b8ca967f25f0
5
5
  SHA512:
6
- metadata.gz: 65e716d2e0cecb340f2e8171d125d901f7eb8e53352227fa472e4489da1c8b97c04000ae6bb59080f2a7d811e9cc2c1dad3248d9624a00f90fba3b2ce55c7e93
7
- data.tar.gz: 4bb8b248e3f0bc57767bc0be615a27d31d663f89e2d8eddd67e30102cbbfe6d49f1bd119db3b6a3fae5593f0751b5862e1af993e90e0f6514e788faadb8d6314
6
+ metadata.gz: eb8d8c1f01f4034d17c474cb8e2561a5a0b74b34aef294f464891619fabed9f8210653d19c20115cb7a5883ca141054244c13d1fa26e258f489ef14c0365b569
7
+ data.tar.gz: db392a7a94d3857e1db00fa59dad6739d6c8292bfcffccd71c2684c98a80332afe36f90c452cd4ed5ed48cba5ce27f48129ffc3d87a9969658131078b7e750bc
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubocop-migration (0.8.0)
4
+ rubocop-migration (0.9.0)
5
5
  activesupport
6
6
  lint_roller (>= 1.1)
7
7
  rubocop (>= 1.72.1)
@@ -31,9 +31,21 @@ module RuboCop
31
31
  # validate_check_constraint :orders, name: 'orders_price_positive'
32
32
  # end
33
33
  # end
34
+ #
35
+ # # good
36
+ # class CreateOrders < ActiveRecord::Migration[7.0]
37
+ # def change
38
+ # create_table :orders do |t|
39
+ # t.integer :price
40
+ # end
41
+ # add_check_constraint :orders, 'price > 0', name: 'orders_price_positive'
42
+ # end
43
+ # end
34
44
  class AddCheckConstraint < RuboCop::Cop::Base
35
45
  extend AutoCorrector
36
46
 
47
+ include ::RuboCop::Migration::CopConcerns::NewTable
48
+
37
49
  MSG = 'Activate a check constraint in a separate migration in PostgreSQL.'
38
50
 
39
51
  RESTRICT_ON_SEND = %i[
@@ -104,7 +116,8 @@ module RuboCop
104
116
  # @return [Boolean]
105
117
  def bad?(node)
106
118
  add_check_constraint?(node) &&
107
- !add_check_constraint_with_validate_false?(node)
119
+ !add_check_constraint_with_validate_false?(node) &&
120
+ !new_table?(node.first_argument)
108
121
  end
109
122
  end
110
123
  end
@@ -27,11 +27,22 @@ module RuboCop
27
27
  # change_column_default :users, :some_column, 'some value'
28
28
  # end
29
29
  # end
30
+ #
31
+ # # good
32
+ # class CreateUsers < ActiveRecord::Migration[7.0]
33
+ # def change
34
+ # create_table :users do |t|
35
+ # t.string :name
36
+ # end
37
+ # add_column :users, :some_column, :string, default: 'some value'
38
+ # end
39
+ # end
30
40
  class AddColumnWithDefaultValue < RuboCop::Cop::Base
31
41
  extend AutoCorrector
32
42
 
33
43
  include RangeHelp
34
44
  include ::RuboCop::Migration::CopConcerns::ColumnTypeMethod
45
+ include ::RuboCop::Migration::CopConcerns::NewTable
35
46
 
36
47
  MSG = 'Add the column without a default value then change the default.'
37
48
 
@@ -44,6 +55,7 @@ module RuboCop
44
55
  # @return [void]
45
56
  def on_send(node)
46
57
  return unless target_method?(node)
58
+ return if new_table?(find_table_node_from(node))
47
59
 
48
60
  default_option_node = non_nil_default_option_node_from(node)
49
61
  return unless default_option_node
@@ -100,7 +100,7 @@ module RuboCop
100
100
  case node.type
101
101
  when :array
102
102
  node.values.count
103
- when :sym
103
+ when :str, :sym
104
104
  1
105
105
  end
106
106
  end
@@ -26,10 +26,21 @@ module RuboCop
26
26
  # add_index :users, :name, algorithm: :concurrently
27
27
  # end
28
28
  # end
29
+ #
30
+ # # good
31
+ # class CreateUsers < ActiveRecord::Migration[7.0]
32
+ # def change
33
+ # create_table :users do |t|
34
+ # t.string :name
35
+ # end
36
+ # add_index :users, :name
37
+ # end
38
+ # end
29
39
  class AddIndexConcurrently < RuboCop::Cop::Base
30
40
  extend AutoCorrector
31
41
 
32
42
  include ::RuboCop::Migration::CopConcerns::DisableDdlTransaction
43
+ include ::RuboCop::Migration::CopConcerns::NewTable
33
44
  include RangeHelp
34
45
 
35
46
  MSG = 'Use `algorithm: :concurrently` on adding indexes to existing tables in PostgreSQL.'
@@ -44,7 +55,7 @@ module RuboCop
44
55
  # @param node [RuboCop::AST::SendNode]
45
56
  # @return [void]
46
57
  def on_send(node)
47
- if add_index_without_concurrency?(node)
58
+ if add_index_without_concurrency?(node) && !new_table?(find_table_node_from(node))
48
59
  add_offense(node) do |corrector|
49
60
  autocorrect(corrector, node)
50
61
  end
@@ -159,6 +170,17 @@ module RuboCop
159
170
  disable_ddl_transactions_from(node)[1..] || []
160
171
  end
161
172
 
173
+ # @param node [RuboCop::AST::SendNode]
174
+ # @return [RuboCop::AST::Node, nil]
175
+ def find_table_node_from(node)
176
+ case node.method_name
177
+ when :add_index
178
+ node.first_argument
179
+ else
180
+ node.each_ancestor(:block).first.send_node.first_argument
181
+ end
182
+ end
183
+
162
184
  # @param node [RuboCop::AST::SendNode]
163
185
  # @return [Boolean]
164
186
  def in_change_table?(node)
@@ -28,16 +28,33 @@ module RuboCop
28
28
  #
29
29
  # class ReplaceNotNullConstraintOnUsersName < ActiveRecord::Migration[7.0]
30
30
  # def change
31
- # validate_constraint :users, name: 'users_name_is_not_null'
31
+ # validate_check_constraint :users, name: 'users_name_is_not_null'
32
32
  # change_column_null :users, :name, false
33
33
  # remove_check_constraint :users, name: 'users_name_is_not_null'
34
34
  # end
35
35
  # end
36
+ #
37
+ # # bad
38
+ # class AllowNullOnUsersName < ActiveRecord::Migration[7.0]
39
+ # def change
40
+ # change_column_null :users, :name, true
41
+ # end
42
+ # end
43
+ #
44
+ # # good
45
+ # class AllowNullOnUsersName < ActiveRecord::Migration[7.0]
46
+ # def up
47
+ # change_column_null :users, :name, true
48
+ # end
49
+ # end
36
50
  class ChangeColumnNull < RuboCop::Cop::Base
37
51
  extend AutoCorrector
38
52
 
39
53
  MSG = 'Avoid simply setting `NOT NULL` constraint on an existing column in PostgreSQL.'
40
54
 
55
+ MSG_FOR_ALLOW_NULL_IN_CHANGE = 'Allow NULL in `up` instead of `change`, ' \
56
+ 'as rolling back `change` simply sets `NOT NULL` constraint again.'
57
+
41
58
  RESTRICT_ON_SEND = %i[
42
59
  change_column_null
43
60
  change_null
@@ -48,6 +65,11 @@ module RuboCop
48
65
  def on_send(node)
49
66
  return if called_with_validate_constraint?(node)
50
67
 
68
+ if allow_null?(node)
69
+ add_offense(node, message: MSG_FOR_ALLOW_NULL_IN_CHANGE) if in_change_method?(node)
70
+ return
71
+ end
72
+
51
73
  add_offense(node) do |corrector|
52
74
  autocorrect(corrector, node)
53
75
  end
@@ -56,11 +78,21 @@ module RuboCop
56
78
 
57
79
  private
58
80
 
81
+ # @!method allow_null?(node)
82
+ # @param node [RuboCop::AST::SendNode]
83
+ # @return [Boolean]
84
+ def_node_matcher :allow_null?, <<~PATTERN
85
+ {
86
+ (send nil? :change_column_null _ _ true ...)
87
+ (call _ :change_null _ true ...)
88
+ }
89
+ PATTERN
90
+
59
91
  # @!method validate_constraint?(node)
60
92
  # @param node [RuboCop::AST::SendNode]
61
93
  # @return [Boolean]
62
94
  def_node_matcher :validate_constraint?, <<~PATTERN
63
- (send nil? :validate_constraint ...)
95
+ (send nil? {:validate_constraint :validate_check_constraint} ...)
64
96
  PATTERN
65
97
 
66
98
  # @param corrector [RuboCop::Cop::Corrector]
@@ -203,6 +235,12 @@ module RuboCop
203
235
  constraint_name: "#{table_name}_#{column_name}_is_not_null"
204
236
  )
205
237
  end
238
+
239
+ # @param node [RuboCop::AST::SendNode]
240
+ # @return [Boolean]
241
+ def in_change_method?(node)
242
+ node.each_ancestor(:def).first&.method?(:change) || false
243
+ end
206
244
  end
207
245
  end
208
246
  end
@@ -121,9 +121,10 @@ module RuboCop
121
121
  # @return [Array<String>]
122
122
  def find_ignored_column_names_from(table_name)
123
123
  pathname = model_pathname_from(
124
- singularize(table_name)
124
+ directory: ::Pathname.new('app/models'),
125
+ snake_cased_model_name: singularize(table_name)
125
126
  )
126
- return [] unless pathname.exist?
127
+ return [] unless pathname
127
128
 
128
129
  ignored_column_nodes = ignored_column_nodes_from(
129
130
  parse(
@@ -155,10 +156,28 @@ module RuboCop
155
156
  )
156
157
  end
157
158
 
159
+ # @note Tries `accounts_user.rb` first, then namespaces such as `accounts/user.rb`.
160
+ # @param directory [Pathname]
158
161
  # @param snake_cased_model_name [String]
159
- # @return [Pathname]
160
- def model_pathname_from(snake_cased_model_name)
161
- ::Pathname.new("app/models/#{snake_cased_model_name}.rb")
162
+ # @return [Pathname, nil]
163
+ def model_pathname_from(
164
+ directory:,
165
+ snake_cased_model_name:
166
+ )
167
+ pathname = directory.join("#{snake_cased_model_name}.rb")
168
+ return pathname if pathname.exist?
169
+
170
+ snake_cased_model_name.scan(/(?<=[^_])_(?=[^_])/) do
171
+ namespace = ::Regexp.last_match.pre_match
172
+ next unless directory.join(namespace).directory?
173
+
174
+ pathname = model_pathname_from(
175
+ directory: directory.join(namespace),
176
+ snake_cased_model_name: ::Regexp.last_match.post_match
177
+ )
178
+ return pathname if pathname
179
+ end
180
+ nil
162
181
  end
163
182
 
164
183
  # @param content [String]
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Migration
5
+ module CopConcerns
6
+ module NewTable
7
+ class << self
8
+ def included(klass)
9
+ super
10
+ klass.class_eval do
11
+ # @!method create_table_if_not_exists?(node)
12
+ # @param node [RuboCop::AST::SendNode]
13
+ # @return [Boolean]
14
+ def_node_matcher :create_table_if_not_exists?, <<~PATTERN
15
+ (send
16
+ nil?
17
+ :create_table
18
+ _
19
+ (hash
20
+ <
21
+ (pair
22
+ (sym :if_not_exists)
23
+ true
24
+ )
25
+ ...
26
+ >
27
+ )
28
+ )
29
+ PATTERN
30
+
31
+ # @!method create_table_name_from(node)
32
+ # @param node [RuboCop::AST::SendNode]
33
+ # @return [String, Symbol, nil]
34
+ def_node_matcher :create_table_name_from, <<~PATTERN
35
+ (send
36
+ nil?
37
+ :create_table
38
+ ({str sym} $_)
39
+ ...
40
+ )
41
+ PATTERN
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # @param node [RuboCop::AST::Node]
49
+ # @param table_name [String]
50
+ # @return [Boolean]
51
+ def create_table_of?(
52
+ node,
53
+ table_name
54
+ )
55
+ node = node.send_node if node.block_type?
56
+ node.send_type? &&
57
+ create_table_name_from(node)&.to_s == table_name &&
58
+ !create_table_if_not_exists?(node)
59
+ end
60
+
61
+ # Whether the table is created by a `create_table` written directly in the method body before it,
62
+ # not under a condition, a block, or a rescue.
63
+ # @param table_node [RuboCop::AST::Node, nil]
64
+ # @return [Boolean]
65
+ def new_table?(table_node)
66
+ return false unless table_node&.type?(:str, :sym)
67
+
68
+ body_node = table_node.each_ancestor(:def, :defs).first&.body
69
+ return false unless body_node&.begin_type?
70
+
71
+ table_name = table_node.value.to_s
72
+ body_node.each_child_node.any? do |statement_node|
73
+ statement_node.source_range.end_pos < table_node.source_range.begin_pos &&
74
+ create_table_of?(statement_node, table_name)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -6,6 +6,7 @@ module RuboCop
6
6
  autoload :BatchProcessing, 'rubocop/migration/cop_concerns/batch_processing'
7
7
  autoload :ColumnTypeMethod, 'rubocop/migration/cop_concerns/column_type_method'
8
8
  autoload :DisableDdlTransaction, 'rubocop/migration/cop_concerns/disable_ddl_transaction'
9
+ autoload :NewTable, 'rubocop/migration/cop_concerns/new_table'
9
10
  end
10
11
  end
11
12
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Migration
5
- VERSION = '0.8.0'
5
+ VERSION = '0.9.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-migration
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
@@ -104,6 +104,7 @@ files:
104
104
  - lib/rubocop/migration/cop_concerns/batch_processing.rb
105
105
  - lib/rubocop/migration/cop_concerns/column_type_method.rb
106
106
  - lib/rubocop/migration/cop_concerns/disable_ddl_transaction.rb
107
+ - lib/rubocop/migration/cop_concerns/new_table.rb
107
108
  - lib/rubocop/migration/plugin.rb
108
109
  - lib/rubocop/migration/version.rb
109
110
  - rubocop-migration.gemspec