rubocop-rails 2.22.2 → 2.25.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -9
  3. data/config/default.yml +13 -4
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +15 -3
  5. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +1 -1
  6. data/lib/rubocop/cop/mixin/target_rails_version.rb +29 -2
  7. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +2 -0
  8. data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
  9. data/lib/rubocop/cop/rails/active_support_on_load.rb +21 -1
  10. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
  11. data/lib/rubocop/cop/rails/content_tag.rb +1 -1
  12. data/lib/rubocop/cop/rails/dangerous_column_names.rb +1 -2
  13. data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
  14. data/lib/rubocop/cop/rails/find_by.rb +3 -3
  15. data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
  16. data/lib/rubocop/cop/rails/http_status.rb +12 -2
  17. data/lib/rubocop/cop/rails/inquiry.rb +1 -0
  18. data/lib/rubocop/cop/rails/not_null_column.rb +91 -13
  19. data/lib/rubocop/cop/rails/pick.rb +10 -5
  20. data/lib/rubocop/cop/rails/pluck.rb +1 -1
  21. data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
  22. data/lib/rubocop/cop/rails/pluck_in_where.rb +18 -5
  23. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +1 -2
  24. data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
  25. data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
  26. data/lib/rubocop/cop/rails/save_bang.rb +2 -0
  27. data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
  28. data/lib/rubocop/cop/rails/time_zone.rb +2 -1
  29. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -4
  30. data/lib/rubocop/cop/rails/unknown_env.rb +1 -1
  31. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
  32. data/lib/rubocop/cop/rails/validation.rb +5 -3
  33. data/lib/rubocop/cop/rails/where_equals.rb +3 -2
  34. data/lib/rubocop/cop/rails/where_exists.rb +9 -8
  35. data/lib/rubocop/cop/rails/where_missing.rb +6 -2
  36. data/lib/rubocop/cop/rails/where_not.rb +8 -6
  37. data/lib/rubocop/cop/rails/where_range.rb +157 -0
  38. data/lib/rubocop/cop/rails_cops.rb +1 -0
  39. data/lib/rubocop/rails/schema_loader/schema.rb +1 -0
  40. data/lib/rubocop/rails/schema_loader.rb +5 -15
  41. data/lib/rubocop/rails/version.rb +1 -1
  42. metadata +7 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54ac592e4d6ccfd065a9f61d33b9305f0a54cc879af4963b7a0a5f13e34ef5f7
4
- data.tar.gz: e7d9a2dc53b76a3b8308b4cb1c2023f1a18d133efd7e14ad0efd43733cb5ffe1
3
+ metadata.gz: cad5e6d0c6f188b6ff87adb04fbfb73c7c574b901de45256eb7fa6e50b7a136d
4
+ data.tar.gz: c8ab8f1d1c3284054af5847231dd996df24ee5441f638f95bc7e32420a3baf04
5
5
  SHA512:
6
- metadata.gz: e5b230f2623f63afcdba2fd1fd5dc415538e47ab283bc78c40715c3e6d10c8b8af92bd3597c87aa7105aded11dde0ec66e79f857675e57ebff7a3455e59a102e
7
- data.tar.gz: 27092e6dc744ce084d4b1eb8eb85c339bc389de861ab6f82cb2956a550921500d4eb012dc74ff702315c6a39dde7b71b7a43ebec70bd8834ba9314365c45b8ea
6
+ metadata.gz: ea0ebe45e988d115aa45d7bdba2b728b0583e1e2d996d43af424eb8abc17ced35b528741fcc47b8b258de52edd2fb7bc1c92dd609b49ddeeaaaf26bce123e783
7
+ data.tar.gz: 7eb06cfce19f2a4ba78c6d571c65cf386e211b48ca2c9b5b9ca436ec9055e49f9737cbb3f9ce1b44ab432de87121295439effc59f093446cea88199bd08ba8a4
data/README.md CHANGED
@@ -66,17 +66,15 @@ end
66
66
  ## Rails configuration tip
67
67
 
68
68
  If you are using Rails 6.1 or newer, add the following `config.generators.after_generate` setting to
69
- your config/application.rb to apply RuboCop autocorrection to code generated by `bin/rails g`.
69
+ your `config/environments/development.rb` to apply RuboCop autocorrection to code generated by `bin/rails g`.
70
70
 
71
71
  ```ruby
72
- # config/application.rb
73
- module YourCoolApp
74
- class Application < Rails::Application
75
- config.generators.after_generate do |files|
76
- parsable_files = files.filter { |file| file.end_with?('.rb') }
77
- unless parsable_files.empty?
78
- system("bundle exec rubocop -A --fail-level=E #{parsable_files.shelljoin}", exception: true)
79
- end
72
+ # config/environments/development.rb
73
+ Rails.application.configure do
74
+ config.generators.after_generate do |files|
75
+ parsable_files = files.filter { |file| file.end_with?('.rb') }
76
+ unless parsable_files.empty?
77
+ system("bundle exec rubocop -A --fail-level=E #{parsable_files.shelljoin}", exception: true)
80
78
  end
81
79
  end
82
80
  end
data/config/default.yml CHANGED
@@ -165,6 +165,7 @@ Rails/ActiveSupportOnLoad:
165
165
  - 'https://guides.rubyonrails.org/engines.html#available-load-hooks'
166
166
  SafeAutoCorrect: false
167
167
  VersionAdded: '2.16'
168
+ VersionChanged: '2.24'
168
169
 
169
170
  Rails/AddColumnIndex:
170
171
  Description: >-
@@ -445,9 +446,10 @@ Rails/EnvironmentVariableAccess:
445
446
  # TODO: Set to `pending` status in RuboCop Rails 2 series when migration doc will be written.
446
447
  Enabled: false
447
448
  VersionAdded: '2.10'
448
- VersionChanged: '2.11'
449
+ VersionChanged: '2.24'
449
450
  Include:
450
451
  - app/**/*.rb
452
+ - config/initializers/**/*.rb
451
453
  - lib/**/*.rb
452
454
  Exclude:
453
455
  - lib/**/*.rake
@@ -691,7 +693,7 @@ Rails/NegateInclude:
691
693
  VersionChanged: '2.9'
692
694
 
693
695
  Rails/NotNullColumn:
694
- Description: 'Do not add a NOT NULL column without a default value.'
696
+ Description: 'Do not add a NOT NULL column without a default value to existing tables.'
695
697
  Enabled: true
696
698
  VersionAdded: '0.43'
697
699
  VersionChanged: '2.20'
@@ -902,7 +904,7 @@ Rails/RequireDependency:
902
904
  VersionAdded: '2.10'
903
905
 
904
906
  Rails/ResponseParsedBody:
905
- Description: Prefer `response.parsed_body` to `JSON.parse(response.body)`.
907
+ Description: Prefer `response.parsed_body` to custom parsing logic for `response.body`.
906
908
  Enabled: pending
907
909
  Safe: false
908
910
  VersionAdded: '2.18'
@@ -1161,8 +1163,9 @@ Rails/UnknownEnv:
1161
1163
 
1162
1164
  Rails/UnusedIgnoredColumns:
1163
1165
  Description: 'Remove a column that does not exist from `ignored_columns`.'
1164
- Enabled: pending
1166
+ Enabled: false
1165
1167
  VersionAdded: '2.11'
1168
+ VersionChanged: '2.25'
1166
1169
  Include:
1167
1170
  - app/models/**/*.rb
1168
1171
 
@@ -1219,6 +1222,12 @@ Rails/WhereNotWithMultipleConditions:
1219
1222
  VersionAdded: '2.17'
1220
1223
  VersionChanged: '2.18'
1221
1224
 
1225
+ Rails/WhereRange:
1226
+ Description: 'Use ranges in `where` instead of manually constructing SQL.'
1227
+ StyleGuide: 'https://rails.rubystyle.guide/#where-ranges'
1228
+ Enabled: pending
1229
+ VersionAdded: '2.25'
1230
+
1222
1231
  # Accept `redirect_to(...) and return` and similar cases.
1223
1232
  Style/AndOr:
1224
1233
  EnforcedStyle: conditionals
@@ -39,7 +39,12 @@ module RuboCop
39
39
  end
40
40
 
41
41
  def schema
42
- RuboCop::Rails::SchemaLoader.load(target_ruby_version)
42
+ # For compatibility with RuboCop 1.61.0 or lower.
43
+ if respond_to?(:parser_engine)
44
+ RuboCop::Rails::SchemaLoader.load(target_ruby_version, parser_engine)
45
+ else
46
+ RuboCop::Rails::SchemaLoader.load(target_ruby_version, :parser_whitequark)
47
+ end
43
48
  end
44
49
 
45
50
  def table_name(class_node)
@@ -98,8 +103,15 @@ module RuboCop
98
103
  end
99
104
 
100
105
  def in_where?(node)
101
- send_node = node.each_ancestor(:send).first
102
- send_node && WHERE_METHODS.include?(send_node.method_name)
106
+ send_node = node.each_ancestor(:send, :csend).first
107
+ return false unless send_node
108
+
109
+ return true if WHERE_METHODS.include?(send_node.method_name)
110
+
111
+ receiver = send_node.receiver
112
+ return false unless receiver&.send_type?
113
+
114
+ send_node.method?(:not) && WHERE_METHODS.include?(receiver.method_name)
103
115
  end
104
116
  end
105
117
  end
@@ -23,7 +23,7 @@ module RuboCop
23
23
  case database_adapter
24
24
  when 'mysql2', 'trilogy'
25
25
  MYSQL
26
- when 'postgresql'
26
+ when 'postgresql', 'postgis'
27
27
  POSTGRESQL
28
28
  end
29
29
  end
@@ -4,13 +4,40 @@ module RuboCop
4
4
  module Cop
5
5
  # Common functionality for checking target rails version.
6
6
  module TargetRailsVersion
7
+ # Informs the base RuboCop gem that it the Rails version is checked via `requires_gem` API,
8
+ # without needing to call this `#support_target_rails_version` method.
9
+ USES_REQUIRES_GEM_API = true
10
+
7
11
  def minimum_target_rails_version(version)
8
- @minimum_target_rails_version = version
12
+ if respond_to?(:requires_gem)
13
+ case version
14
+ when Integer, Float then requires_gem(TARGET_GEM_NAME, ">= #{version}")
15
+ when String then requires_gem(TARGET_GEM_NAME, version)
16
+ end
17
+ else
18
+ # Fallback path for previous versions of RuboCop which don't support the `requires_gem` API yet.
19
+ @minimum_target_rails_version = version
20
+ end
9
21
  end
10
22
 
11
23
  def support_target_rails_version?(version)
12
- @minimum_target_rails_version <= version
24
+ if respond_to?(:requires_gem)
25
+ return false unless gem_requirements
26
+
27
+ gem_requirement = gem_requirements[TARGET_GEM_NAME]
28
+ return true unless gem_requirement # If we have no requirement, then we support all versions
29
+
30
+ gem_requirement.satisfied_by?(Gem::Version.new(version))
31
+ else
32
+ # Fallback path for previous versions of RuboCop which don't support the `requires_gem` API yet.
33
+ @minimum_target_rails_version <= version
34
+ end
13
35
  end
36
+
37
+ # Look for `railties` instead of `rails`, to support apps that only use a subset of `rails`
38
+ # See https://github.com/rubocop/rubocop/pull/11289
39
+ TARGET_GEM_NAME = 'railties'
40
+ private_constant :TARGET_GEM_NAME
14
41
  end
15
42
  end
16
43
  end
@@ -99,6 +99,8 @@ module RuboCop
99
99
 
100
100
  def use_redirect_to?(context)
101
101
  context.right_siblings.compact.any? do |sibling|
102
+ # Unwrap `return redirect_to :index`
103
+ sibling = sibling.children.first if sibling.return_type? && sibling.children.one?
102
104
  sibling.send_type? && sibling.method?(:redirect_to)
103
105
  end
104
106
  end
@@ -27,13 +27,13 @@ module RuboCop
27
27
 
28
28
  ALIASES = {
29
29
  starts_with?: {
30
- original: :start_with?, matcher: '(send str :starts_with? _)'
30
+ original: :start_with?, matcher: '(call str :starts_with? _)'
31
31
  },
32
32
  ends_with?: {
33
- original: :end_with?, matcher: '(send str :ends_with? _)'
33
+ original: :end_with?, matcher: '(call str :ends_with? _)'
34
34
  },
35
- append: { original: :<<, matcher: '(send array :append _)' },
36
- prepend: { original: :unshift, matcher: '(send array :prepend _)' }
35
+ append: { original: :<<, matcher: '(call array :append _)' },
36
+ prepend: { original: :unshift, matcher: '(call array :prepend _)' }
37
37
  }.freeze
38
38
 
39
39
  ALIASES.each do |aliased_method, options|
@@ -47,13 +47,14 @@ module RuboCop
47
47
  preferred_method = ALIASES[aliased_method][:original]
48
48
  message = format(MSG, prefer: preferred_method, current: aliased_method)
49
49
 
50
- add_offense(node, message: message) do |corrector|
50
+ add_offense(node.loc.selector.join(node.source_range.end), message: message) do |corrector|
51
51
  next if append(node)
52
52
 
53
53
  corrector.replace(node.loc.selector, preferred_method)
54
54
  end
55
55
  end
56
56
  end
57
+ alias on_csend on_send
57
58
  end
58
59
  end
59
60
  end
@@ -55,15 +55,35 @@ module RuboCop
55
55
  'ActiveSupport::TestCase' => 'active_support_test_case'
56
56
  }.freeze
57
57
 
58
+ RAILS_5_2_LOAD_HOOKS = {
59
+ 'ActiveRecord::ConnectionAdapters::SQLite3Adapter' => 'active_record_sqlite3adapter'
60
+ }.freeze
61
+
62
+ RAILS_7_1_LOAD_HOOKS = {
63
+ 'ActiveRecord::TestFixtures' => 'active_record_fixtures',
64
+ 'ActiveModel::Model' => 'active_model',
65
+ 'ActionText::EncryptedRichText' => 'action_text_encrypted_rich_text',
66
+ 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter' => 'active_record_postgresqladapter',
67
+ 'ActiveRecord::ConnectionAdapters::Mysql2Adapter' => 'active_record_mysql2adapter',
68
+ 'ActiveRecord::ConnectionAdapters::TrilogyAdapter' => 'active_record_trilogyadapter'
69
+ }.freeze
70
+
58
71
  def on_send(node)
59
72
  receiver, method, arguments = *node # rubocop:disable InternalAffairs/NodeDestructuring
60
- return unless receiver && (hook = LOAD_HOOKS[receiver.const_name])
73
+ return unless arguments && (hook = hook_for_const(receiver&.const_name))
61
74
 
62
75
  preferred = "ActiveSupport.on_load(:#{hook}) { #{method} #{arguments.source} }"
63
76
  add_offense(node, message: format(MSG, prefer: preferred, current: node.source)) do |corrector|
64
77
  corrector.replace(node, preferred)
65
78
  end
66
79
  end
80
+
81
+ def hook_for_const(const_name)
82
+ hook = LOAD_HOOKS[const_name]
83
+ hook ||= RAILS_5_2_LOAD_HOOKS[const_name] if target_rails_version >= 5.2
84
+ hook ||= RAILS_7_1_LOAD_HOOKS[const_name] if target_rails_version >= 7.1
85
+ hook
86
+ end
67
87
  end
68
88
  end
69
89
  end
@@ -14,7 +14,7 @@ module RuboCop
14
14
  # automatically detect an adapter from `development` environment
15
15
  # in `config/database.yml` or the environment variable `DATABASE_URL`
16
16
  # when the `Database` option is not set.
17
- # If the adapter is not `mysql2`, `trilogy`, or `postgresql`,
17
+ # If the adapter is not `mysql2`, `trilogy`, `postgresql`, or `postgis`,
18
18
  # this Cop ignores offenses.
19
19
  #
20
20
  # @example
@@ -7,7 +7,7 @@ module RuboCop
7
7
  #
8
8
  # NOTE: Allow `tag` when the first argument is a variable because
9
9
  # `tag(name)` is simpler rather than `tag.public_send(name)`.
10
- # And this cop will be renamed to something like `LegacyTag` in the future. (e.g. RuboCop Rails 2.0)
10
+ # And this cop will be renamed to something like `LegacyTag` in the future. (e.g. RuboCop Rails 3.0)
11
11
  #
12
12
  # @example
13
13
  # # bad
@@ -31,7 +31,7 @@ module RuboCop
31
31
  time
32
32
  ].to_set.freeze
33
33
 
34
- # Generated from `ActiveRecord::AttributeMethods.dangerous_attribute_methods` on activerecord 7.1.0.
34
+ # Generated from `ActiveRecord::AttributeMethods.dangerous_attribute_methods` on activerecord 7.1.3.
35
35
  # rubocop:disable Metrics/CollectionLiteralLength
36
36
  DANGEROUS_COLUMN_NAMES = %w[
37
37
  __callbacks
@@ -290,7 +290,6 @@ module RuboCop
290
290
  new_record
291
291
  no_touching
292
292
  normalize_reflection_attribute
293
- object_id
294
293
  partial_inserts
295
294
  partial_updates
296
295
  perform_validations
@@ -51,7 +51,7 @@ module RuboCop
51
51
  return if allow?(begin_node, end_node)
52
52
 
53
53
  preferred_method = preferred_method(begin_node)
54
- if begin_node.method?(:beginning_of_week) && begin_node.arguments.one?
54
+ if begin_node.method?(:beginning_of_week) && begin_node.arguments.one? && end_node.arguments.one?
55
55
  return unless same_argument?(begin_node, end_node)
56
56
 
57
57
  preferred_method << "(#{begin_node.first_argument.source})"
@@ -28,7 +28,7 @@ module RuboCop
28
28
  include RangeHelp
29
29
  extend AutoCorrector
30
30
 
31
- MSG = 'Use `find_by` instead of `where.%<method>s`.'
31
+ MSG = 'Use `find_by` instead of `where%<dot>s%<method>s`.'
32
32
  RESTRICT_ON_SEND = %i[first take].freeze
33
33
 
34
34
  def on_send(node)
@@ -37,7 +37,7 @@ module RuboCop
37
37
 
38
38
  range = offense_range(node)
39
39
 
40
- add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
40
+ add_offense(range, message: format(MSG, dot: node.loc.dot.source, method: node.method_name)) do |corrector|
41
41
  autocorrect(corrector, node)
42
42
  end
43
43
  end
@@ -59,7 +59,7 @@ module RuboCop
59
59
  return if node.method?(:first)
60
60
 
61
61
  where_loc = node.receiver.loc.selector
62
- first_loc = range_between(node.loc.dot.begin_pos, node.loc.selector.end_pos)
62
+ first_loc = range_between(node.receiver.source_range.end_pos, node.loc.selector.end_pos)
63
63
 
64
64
  corrector.replace(where_loc, 'find_by')
65
65
  corrector.replace(first_loc, '')
@@ -24,40 +24,39 @@ module RuboCop
24
24
  RESTRICT_ON_SEND = %i[take! find_by_id! find_by!].freeze
25
25
 
26
26
  def_node_matcher :where_take?, <<~PATTERN
27
- (send
28
- $(send _ :where
27
+ (call
28
+ $(call _ :where
29
29
  (hash
30
30
  (pair (sym :id) $_))) :take!)
31
31
  PATTERN
32
32
 
33
33
  def_node_matcher :find_by?, <<~PATTERN
34
34
  {
35
- (send _ :find_by_id! $_)
36
- (send _ :find_by! (hash (pair (sym :id) $_)))
35
+ (call _ :find_by_id! $_)
36
+ (call _ :find_by! (hash (pair (sym :id) $_)))
37
37
  }
38
38
  PATTERN
39
39
 
40
40
  def on_send(node)
41
41
  where_take?(node) do |where, id_value|
42
42
  range = where_take_offense_range(node, where)
43
- bad_method = build_where_take_bad_method(id_value)
44
43
 
45
- register_offense(range, id_value, bad_method)
44
+ register_offense(range, id_value)
46
45
  end
47
46
 
48
47
  find_by?(node) do |id_value|
49
48
  range = find_by_offense_range(node)
50
- bad_method = build_find_by_bad_method(node, id_value)
51
49
 
52
- register_offense(range, id_value, bad_method)
50
+ register_offense(range, id_value)
53
51
  end
54
52
  end
53
+ alias on_csend on_send
55
54
 
56
55
  private
57
56
 
58
- def register_offense(range, id_value, bad_method)
57
+ def register_offense(range, id_value)
59
58
  good_method = build_good_method(id_value)
60
- message = format(MSG, good_method: good_method, bad_method: bad_method)
59
+ message = format(MSG, good_method: good_method, bad_method: range.source)
61
60
 
62
61
  add_offense(range, message: message) do |corrector|
63
62
  corrector.replace(range, good_method)
@@ -75,19 +74,6 @@ module RuboCop
75
74
  def build_good_method(id_value)
76
75
  "find(#{id_value.source})"
77
76
  end
78
-
79
- def build_where_take_bad_method(id_value)
80
- "where(id: #{id_value.source}).take!"
81
- end
82
-
83
- def build_find_by_bad_method(node, id_value)
84
- case node.method_name
85
- when :find_by_id!
86
- "find_by_id!(#{id_value.source})"
87
- when :find_by!
88
- "find_by!(id: #{id_value.source})"
89
- end
90
- end
91
77
  end
92
78
  end
93
79
  end
@@ -13,6 +13,8 @@ module RuboCop
13
13
  # render plain: 'foo/bar', status: 304
14
14
  # redirect_to root_url, status: 301
15
15
  # head 200
16
+ # assert_response 200
17
+ # assert_redirected_to '/some/path', status: 301
16
18
  #
17
19
  # # good
18
20
  # render :foo, status: :ok
@@ -20,6 +22,8 @@ module RuboCop
20
22
  # render plain: 'foo/bar', status: :not_modified
21
23
  # redirect_to root_url, status: :moved_permanently
22
24
  # head :ok
25
+ # assert_response :ok
26
+ # assert_redirected_to '/some/path', status: :moved_permanently
23
27
  #
24
28
  # @example EnforcedStyle: numeric
25
29
  # # bad
@@ -28,6 +32,8 @@ module RuboCop
28
32
  # render plain: 'foo/bar', status: :not_modified
29
33
  # redirect_to root_url, status: :moved_permanently
30
34
  # head :ok
35
+ # assert_response :ok
36
+ # assert_redirected_to '/some/path', status: :moved_permanently
31
37
  #
32
38
  # # good
33
39
  # render :foo, status: 200
@@ -35,18 +41,22 @@ module RuboCop
35
41
  # render plain: 'foo/bar', status: 304
36
42
  # redirect_to root_url, status: 301
37
43
  # head 200
44
+ # assert_response 200
45
+ # assert_redirected_to '/some/path', status: 301
38
46
  #
39
47
  class HttpStatus < Base
40
48
  include ConfigurableEnforcedStyle
41
49
  extend AutoCorrector
42
50
 
43
- RESTRICT_ON_SEND = %i[render redirect_to head].freeze
51
+ RESTRICT_ON_SEND = %i[render redirect_to head assert_response assert_redirected_to].freeze
44
52
 
45
53
  def_node_matcher :http_status, <<~PATTERN
46
54
  {
47
55
  (send nil? {:render :redirect_to} _ $hash)
48
56
  (send nil? {:render :redirect_to} $hash)
49
- (send nil? :head ${int sym} ...)
57
+ (send nil? {:head :assert_response} ${int sym} ...)
58
+ (send nil? :assert_redirected_to _ $hash ...)
59
+ (send nil? :assert_redirected_to $hash ...)
50
60
  }
51
61
  PATTERN
52
62
 
@@ -33,6 +33,7 @@ module RuboCop
33
33
 
34
34
  add_offense(node.loc.selector)
35
35
  end
36
+ alias on_csend on_send
36
37
  end
37
38
  end
38
39
  end
@@ -3,24 +3,42 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Checks for add_column call with NOT NULL constraint in migration file.
6
+ # Checks for add_column calls with a NOT NULL constraint without a default
7
+ # value.
7
8
  #
8
- # `TEXT` can have default values in PostgreSQL, but not in MySQL.
9
- # It will automatically detect an adapter from `development` environment
10
- # in `config/database.yml` or the environment variable `DATABASE_URL`
11
- # when the `Database` option is not set. If the database is MySQL,
12
- # this cop ignores offenses for the `TEXT`.
9
+ # This cop only applies when adding a column to an existing table, since
10
+ # existing records will not have a value for the new column. New tables
11
+ # can freely use NOT NULL columns without defaults, since there are no
12
+ # records that could violate the constraint.
13
+ #
14
+ # If you need to add a NOT NULL column to an existing table, you must add
15
+ # it as nullable first, back-fill the data, and then use
16
+ # `change_column_null`. Alternatively, you could add the column with a
17
+ # default first to have the database automatically backfill existing rows,
18
+ # and then use `change_column_default` to remove the default.
19
+ #
20
+ # `TEXT` cannot have a default value in MySQL.
21
+ # The cop will automatically detect an adapter from `development`
22
+ # environment in `config/database.yml` or the environment variable
23
+ # `DATABASE_URL` when the `Database` option is not set. If the database
24
+ # is MySQL, this cop ignores offenses for `TEXT` columns.
13
25
  #
14
26
  # @example
15
27
  # # bad
16
28
  # add_column :users, :name, :string, null: false
17
29
  # add_reference :products, :category, null: false
30
+ # change_table :users do |t|
31
+ # t.string :name, null: false
32
+ # end
18
33
  #
19
34
  # # good
20
35
  # add_column :users, :name, :string, null: true
21
36
  # add_column :users, :name, :string, null: false, default: ''
37
+ # change_table :users do |t|
38
+ # t.string :name, null: false, default: ''
39
+ # end
22
40
  # add_reference :products, :category
23
- # add_reference :products, :category, null: false, default: 1
41
+ # change_column_null :products, :category_id, false
24
42
  class NotNullColumn < Base
25
43
  include DatabaseTypeResolvable
26
44
 
@@ -35,6 +53,22 @@ module RuboCop
35
53
  (send nil? :add_reference _ _ (hash $...))
36
54
  PATTERN
37
55
 
56
+ def_node_matcher :change_table?, <<~PATTERN
57
+ (block (send nil? :change_table ...) (args (arg $_)) _)
58
+ PATTERN
59
+
60
+ def_node_matcher :add_not_null_column_in_change_table?, <<~PATTERN
61
+ (send (lvar $_) :column _ $_ (hash $...))
62
+ PATTERN
63
+
64
+ def_node_matcher :add_not_null_column_via_shortcut_in_change_table?, <<~PATTERN
65
+ (send (lvar $_) $_ _ (hash $...))
66
+ PATTERN
67
+
68
+ def_node_matcher :add_not_null_reference_in_change_table?, <<~PATTERN
69
+ (send (lvar $_) :add_reference _ _ (hash $...))
70
+ PATTERN
71
+
38
72
  def_node_matcher :null_false?, <<~PATTERN
39
73
  (pair (sym :null) (false))
40
74
  PATTERN
@@ -48,16 +82,25 @@ module RuboCop
48
82
  check_add_reference(node)
49
83
  end
50
84
 
85
+ def on_block(node)
86
+ check_change_table(node)
87
+ end
88
+ alias on_numblock on_block
89
+
51
90
  private
52
91
 
92
+ def check_column(type, pairs)
93
+ if type.respond_to?(:value)
94
+ return if type.value == :virtual || type.value == 'virtual'
95
+ return if (type.value == :text || type.value == 'text') && database == MYSQL
96
+ end
97
+
98
+ check_pairs(pairs)
99
+ end
100
+
53
101
  def check_add_column(node)
54
102
  add_not_null_column?(node) do |type, pairs|
55
- if type.respond_to?(:value)
56
- return if type.value == :virtual || type.value == 'virtual'
57
- return if (type.value == :text || type.value == 'text') && database == MYSQL
58
- end
59
-
60
- check_pairs(pairs)
103
+ check_column(type, pairs)
61
104
  end
62
105
  end
63
106
 
@@ -67,6 +110,41 @@ module RuboCop
67
110
  end
68
111
  end
69
112
 
113
+ def check_add_column_in_change_table(node, table)
114
+ add_not_null_column_in_change_table?(node) do |receiver, type, pairs|
115
+ next unless receiver == table
116
+
117
+ check_column(type, pairs)
118
+ end
119
+ end
120
+
121
+ def check_add_column_via_shortcut_in_change_table(node, table)
122
+ add_not_null_column_via_shortcut_in_change_table?(node) do |receiver, type, pairs|
123
+ next unless receiver == table
124
+
125
+ check_column(type, pairs)
126
+ end
127
+ end
128
+
129
+ def check_add_reference_in_change_table(node, table)
130
+ add_not_null_reference_in_change_table?(node) do |receiver, pairs|
131
+ next unless receiver == table
132
+
133
+ check_pairs(pairs)
134
+ end
135
+ end
136
+
137
+ def check_change_table(node)
138
+ change_table?(node) do |table|
139
+ children = node.body.begin_type? ? node.body.children : [node.body]
140
+ children.each do |child|
141
+ check_add_column_in_change_table(child, table)
142
+ check_add_column_via_shortcut_in_change_table(child, table)
143
+ check_add_reference_in_change_table(child, table)
144
+ end
145
+ end
146
+ end
147
+
70
148
  def check_pairs(pairs)
71
149
  return if pairs.any? { |pair| default_option?(pair) }
72
150