-
-
Notifications
You must be signed in to change notification settings - Fork 263
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/UniqueValidationWithoutIndex cop #197
Conversation
ac6cf91
to
d23f303
Compare
module Rails | ||
# It loads db/schema.rb and return Schema object. | ||
# Cops refers database schema information with this module. | ||
module SchemaLoader |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the schema loader.
I implemented it as a mixin at first, but it was a bad idea. So now it is a non-mixin module.
If it is a mixin, the @schema
cache only works for a single cop and single file. So it needs to parse db/schema.rb
many times.
But if the non-mixin module, just once parsing is enough.
@@ -496,6 +496,13 @@ Rails/UniqBeforePluck: | |||
- aggressive | |||
AutoCorrect: false | |||
|
|||
Rails/UniqueValidationWithoutIndex: | |||
Description: 'Uniqueness validation should be with a unique index.' | |||
Enabled: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be the status pending
, or not? I'm not sure, so if it should be pending
, please tell me 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RuboCop core has not yet released cop with pending
. Here, let's try with true
first.
Oops, I found a false positive with validation for a relation. # If the table has a unique index for `user_id` column, the following validation is not offensive.
# But the cop registers an offense to the validation.
belongs_to :user
validates :user, uniqueness: true I'll fix it soon. |
d23f303
to
585b2d9
Compare
Fixed and force-pushed. |
|
||
module RuboCop | ||
module Cop | ||
# A mixin to extend cops for ActiveRecord features |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proper notation is Active Record
in this case. Can you update it in this PR?
The proper names of Rails components have a space in between the words, like "Active Support". ActiveRecord is a Ruby module, whereas Active Record is an ORM.
module ActiveRecordHelper | ||
extend NodePattern::Macros | ||
|
||
def_node_search :find_set_table_name, <<-PATTERN |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use squiggly heredoc in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I guess the cause is my old-style snippets. Thank you notice that!
https://github.com/pocke/dotfiles/blob/c5def33338c0a9f4b32086986c0fc4fd6250f4ff/snippets/ruby.snip#L73-L83
class UniqueValidationWithoutIndex < Cop | ||
include ActiveRecordHelper | ||
|
||
MSG = 'Uniqueness validation should be with a unique index' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a period to the end of the message?
rubocop-rails.gemspec
Outdated
@@ -4,6 +4,7 @@ $LOAD_PATH.unshift File.expand_path('lib', __dir__) | |||
require 'rubocop/rails/version' | |||
require 'English' | |||
|
|||
# rubocop:disable Metrics/BlockLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you specify in the exclusion list of this cop at .rubocop.yml?
https://github.com/rubocop-hq/rubocop-rails/blob/v2.4.2/.rubocop.yml#L80-L84
rubocop-rails.gemspec
Outdated
@@ -33,6 +34,8 @@ Gem::Specification.new do |s| | |||
|
|||
# Rack::Utils::SYMBOL_TO_STATUS_CODE, which is used by HttpStatus cop, was | |||
# introduced in rack 1.1 | |||
s.add_runtime_dependency 'activesupport' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This dependency should be added above the comment for Rack :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not confirm the result of sorting the dependencies by rubocop -a
. 🙈
585b2d9
to
b3797d5
Compare
I confirmed the comments and fixed them, then force-pushed. Thank you for reviewing! |
What about if someone has set |
module RuboCop | ||
module Cop | ||
module Rails | ||
# When you define uniqueness validation in Active Record model, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing 'a' before 'define'?
module Cop | ||
module Rails | ||
# When you define uniqueness validation in Active Record model, | ||
# you also should add a unique index for the column. It has two reasons. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'It has two reasons' reads a little awkward – maybe change to 'There are two reasons' or 'There are two reasons for this'?
|
||
# It parses `db/schema.rb` and return it. | ||
# It returns `nil` if it can't find `db/schema.rb`. | ||
# So a cop that uses the loader should handle `nil` properly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about having it raise an exception if schema.rb
is missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I think missing schema.rb
should be handled, it means it shouldn't display a stack trace. Because it is not a bug of rubocop-rails.
But I think it is a good idea to notify missing shcema.rb
to the user. So, how about a warning instead of an exception?
For example: "db/schema.rb
is missing. Disable Rails/UniqueValidationWithoutIndex
cop if you don't use db/schema.rb
."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there is a need to raise warnings or errors. OTOH, documenting the dependency on db/schema.rb will help users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not have a strong opinion for it because I think both of them have pros and cons.
The warning can tell users that the cop does not work. It may be a bug, or using schema.sql instead.
The current behavior conceals it. but it will be easy to use rubocop-rails, because no-configuration is necessary even if the cop doesn't work.
By the way, I updated the documentation to mention existence of db/schema.rb.
https://github.com/rubocop-hq/rubocop-rails/pull/197/files#diff-af4cb6c7e18c12f0a26b2ef45466f731R15
|
||
context 'without db/schema.rb' do | ||
it 'does nothing' do | ||
expect_no_offenses(<<-RUBY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are still some <<-
:-)
Currently the cop does nothing if |
Yeah. I think the cop's responsibility should not handle the existence of db/schema.rb. Each cop responsibility should be simple. So, it should be essentially SRP: Single Responsibility Principle. |
b3797d5
to
9c9eac6
Compare
I applied the reviewed comments, Thanks! @koic I like the both of warning and no-warning ideas, so if you'd like no-warning, could you merge this pull request? |
Yeah, I'm going to merge this one! Thanks! |
rubocop/rubocop-rails#197 introduced a new cop to rubocop-rails that checks for unique validation on an index if it's present in the model. This caused a number of failures when upgrading to 2.5.0 that this fixes.
rubocop/rubocop-rails#197 introduced a new cop to rubocop-rails that checks for unique validation on an index if it's present in the model. This caused a number of failures when upgrading to 2.5.0 that this fixes.
rubocop/rubocop-rails#197 introduced a new cop to rubocop-rails that checks for unique validation on an index if it's present in the model. This caused a number of failures when upgrading to 2.5.0 that this fixes.
rubocop/rubocop-rails#197 introduced a new cop to rubocop-rails that checks for unique validation on an index if it's present in the model. This caused a number of failures when upgrading to 2.5.0 that this fixes.
rubocop/rubocop-rails#197 introduced a new cop to rubocop-rails that checks for unique validation on an index if it's present in the model. This caused a number of failures when upgrading to 2.5.0 that this fixes.
I love this, thank you |
I confirm it does this supports validation with a scope and using relation names instead of id columns!!!
|
Target problem
The target is the ActiveRecord's uniqueness validation.
When you define uniqueness validation in a model, you also should add a unique index.
It has two reasons.
First, duplicated records may occur even if ActiveRecord's validation is defined.
The Rails Guide mentions this problem.
Second, it will cause slow queries.
The validation executes a
SELECT
statement with the target column when inserting/updating a record. If the column does not have an index and the table is large, the query will be heavy.Solution
Add a
Rails/UniqueValidationWithoutIndex
cop to detect the problem.And, I implemented a
db/schema.rb
loader for the cop.So now we can implement cops with DB schema information! I think we can more powerful cops for ActiveRecord with the loader.
Note
This pull request adds
active_support
dependency. But it actually does not increase dependencies in most cases because rubocop-rails is used in Rails projects.Limitation
This cop ignores
validates_uniqueness_of
method in the current implementation.Of course, it's better if the cop supports
validates_uniqueness_of
. But personally I think it is not important. Because Rails/Validation cop marks it "old-style", so I guess not many users use the method, especially rubocop-rails users.But if someone sends a pull request to support the method, it will be acceptable. I just omitted it now to keep the first PR simple.
Before submitting the PR make sure the following are checked:
[Fix #issue-number]
(if the related issue exists).master
(if not - rebase it).and description in grammatically correct, complete sentences.
bundle exec rake default
. It executes all tests and RuboCop for itself, and generates the documentation.