Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Rails/AddColumnIndex cop to prevent passing an unused but confusing index: key to add_column migrations #497

Merged
merged 1 commit into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* [#490](https://github.com/rubocop/rubocop-rails/issues/490): Make `Rails/HttpStatus` aware of `head` method. ([@koic][])
* [#483](https://github.com/rubocop/rubocop-rails/pull/483): Add new `Rails/EagerEvaluationLogMessage` cop. ([@aesthetikx][])
* [#495](https://github.com/rubocop/rubocop-rails/issues/495): Add new `Rails/I18nLocaleAssignment` cop. ([@koic][])
* [#497](https://github.com/rubocop/rubocop-rails/issues/497): Add new `Rails/AddColumnIndex` cop. ([@dvandersluis][])

### Bug fixes

Expand Down
10 changes: 10 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ Rails/ActiveSupportAliases:
Enabled: true
VersionAdded: '0.48'

Rails/AddColumnIndex:
Description: >-
Rails migrations don't make use of a given `index` key, but also
doesn't given an error when it's used, so it makes it seem like an
index might be used.
Enabled: pending
VersionAdded: '2.11'
Include:
- db/migrate/*.rb

Rails/AfterCommitOverride:
Description: >-
This cop enforces that there is only one call to `after_commit`
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ based on the https://rails.rubystyle.guide/[Rails Style Guide].
* xref:cops_rails.adoc#railsactiverecordcallbacksorder[Rails/ActiveRecordCallbacksOrder]
* xref:cops_rails.adoc#railsactiverecordoverride[Rails/ActiveRecordOverride]
* xref:cops_rails.adoc#railsactivesupportaliases[Rails/ActiveSupportAliases]
* xref:cops_rails.adoc#railsaddcolumnindex[Rails/AddColumnIndex]
* xref:cops_rails.adoc#railsaftercommitoverride[Rails/AfterCommitOverride]
* xref:cops_rails.adoc#railsapplicationcontroller[Rails/ApplicationController]
* xref:cops_rails.adoc#railsapplicationjob[Rails/ApplicationJob]
Expand Down
39 changes: 39 additions & 0 deletions docs/modules/ROOT/pages/cops_rails.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,45 @@ are not used.
[1, 2, 'a'].prepend('b')
----

== Rails/AddColumnIndex

|===
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged

| Pending
| Yes
| Yes
| 2.11
| -
|===

This cop checks for migrations using `add_column` that have an `index`
key. `add_column` does not accept `index`, but also does not raise an
error for extra keys, so it is possible to mistakenly add the key without
realizing it will not actually add an index.

=== Examples

[source,ruby]
----
# bad (will not add an index)
add_column :table, :column, :integer, index: true

# good
add_column :table, :column, :integer
add_index :table, :column
----

=== Configurable attributes

|===
| Name | Default value | Configurable values

| Include
| `db/migrate/*.rb`
| Array
|===

== Rails/AfterCommitOverride

|===
Expand Down
64 changes: 64 additions & 0 deletions lib/rubocop/cop/rails/add_column_index.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Rails
# This cop checks for migrations using `add_column` that have an `index`
# key. `add_column` does not accept `index`, but also does not raise an
# error for extra keys, so it is possible to mistakenly add the key without
# realizing it will not actually add an index.
#
# @example
# # bad (will not add an index)
# add_column :table, :column, :integer, index: true
#
# # good
# add_column :table, :column, :integer
# add_index :table, :column
#
class AddColumnIndex < Base
extend AutoCorrector
include RangeHelp

MSG = '`add_column` does not accept an `index` key, use `add_index` instead.'
RESTRICT_ON_SEND = %i[add_column].freeze

# @!method add_column_with_index(node)
def_node_matcher :add_column_with_index, <<~PATTERN
(
send nil? :add_column $_table $_column
<(hash <$(pair {(sym :index) (str "index")} $_) ...>) ...>
)
PATTERN

def on_send(node)
table, column, pair, value = add_column_with_index(node)
return unless pair

add_offense(pair) do |corrector|
corrector.remove(index_range(pair))

add_index = "add_index #{table.source}, #{column.source}"
add_index_opts = ''

if value.hash_type?
hash = value.loc.expression.adjust(begin_pos: 1, end_pos: -1).source.strip
add_index_opts = ", #{hash}"
end

corrector.insert_after(node, "\n#{add_index}#{add_index_opts}")
end
end

private

def index_range(pair_node)
range_with_surrounding_comma(
range_with_surrounding_space(range: pair_node.loc.expression, side: :left),
:left
)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rails_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require_relative 'rails/active_record_callbacks_order'
require_relative 'rails/active_record_override'
require_relative 'rails/active_support_aliases'
require_relative 'rails/add_column_index'
require_relative 'rails/after_commit_override'
require_relative 'rails/application_controller'
require_relative 'rails/application_job'
Expand Down
88 changes: 88 additions & 0 deletions spec/rubocop/cop/rails/add_column_index_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rails::AddColumnIndex, :config do
it 'registers an offense and corrects when an `add_column` call has `index: true`' do
expect_offense(<<~RUBY)
add_column :table, :column, :integer, default: 0, index: true
^^^^^^^^^^^ `add_column` does not accept an `index` key, use `add_index` instead.
RUBY

expect_correction(<<~RUBY)
add_column :table, :column, :integer, default: 0
add_index :table, :column
RUBY
end

it 'registers an offense and corrects when an `add_column` call has `index:` with a hash' do
expect_offense(<<~RUBY)
add_column :table, :column, :integer, default: 0, index: { unique: true, name: 'my_unique_index' }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `add_column` does not accept an `index` key, use `add_index` instead.
RUBY

expect_correction(<<~RUBY)
add_column :table, :column, :integer, default: 0
add_index :table, :column, unique: true, name: 'my_unique_index'
RUBY
end

it 'registers an offense and corrects with there is another hash key after `index`' do
expect_offense(<<~RUBY)
add_column :table, :column, :integer, index: true, default: 0
^^^^^^^^^^^ `add_column` does not accept an `index` key, use `add_index` instead.
RUBY

expect_correction(<<~RUBY)
add_column :table, :column, :integer, default: 0
add_index :table, :column
RUBY
end

it 'registers an offense and corrects with string keys' do
expect_offense(<<~RUBY)
add_column :table, :column, :integer, 'index' => true, default: 0
^^^^^^^^^^^^^^^ `add_column` does not accept an `index` key, use `add_index` instead.
RUBY

expect_correction(<<~RUBY)
add_column :table, :column, :integer, default: 0
add_index :table, :column
RUBY
end

it 'registers an offense and corrects when on multiple lines' do
expect_offense(<<~RUBY)
add_column :table, :column, :integer,
index: true,
^^^^^^^^^^^ `add_column` does not accept an `index` key, use `add_index` instead.
default: 0
RUBY

expect_correction(<<~RUBY)
add_column :table, :column, :integer,
default: 0
add_index :table, :column
RUBY
end

it 'can correct multiple `add_column` calls' do
expect_offense(<<~RUBY)
add_column :table, :column, :integer, default: 0, index: true
^^^^^^^^^^^ `add_column` does not accept an `index` key, use `add_index` instead.
add_column :table, :column2, :integer, default: 0, index: true
^^^^^^^^^^^ `add_column` does not accept an `index` key, use `add_index` instead.
RUBY

expect_correction(<<~RUBY)
add_column :table, :column, :integer, default: 0
add_index :table, :column
add_column :table, :column2, :integer, default: 0
add_index :table, :column2
RUBY
end

it 'does not register an offense without an `index` key to `add_column`' do
expect_no_offenses(<<~RUBY)
add_column :table, :column, :integer, default: 0
RUBY
end
end