class RuboCop::Cop::Rails::WhereExists

This cop enforces consistent style when using `exists?`.

Two styles are supported for this cop. When EnforcedStyle is 'exists' then the cop enforces `exists?(…)` over `where(…).exists?`.

When EnforcedStyle is 'where' then the cop enforces `where(…).exists?` over `exists?(…)`.

This cop is unsafe for auto-correction because the behavior may change on the following case:

source,ruby

Author.includes(:articles).where(articles: {id: id}).exists? #=> Perform `eager_load` behavior (`LEFT JOIN` query) and get result.

Author.includes(:articles).exists?(articles: {id: id}) #=> Perform `preload` behavior and `ActiveRecord::StatementInvalid` error occurs.


@example EnforcedStyle: exists (default)

# bad
User.where(name: 'john').exists?
User.where(['name = ?', 'john']).exists?
User.where('name = ?', 'john').exists?
user.posts.where(published: true).exists?

# good
User.exists?(name: 'john')
User.where('length(name) > 10').exists?
user.posts.exists?(published: true)

@example EnforcedStyle: where

# bad
User.exists?(name: 'john')
User.exists?(['name = ?', 'john'])
User.exists?('name = ?', 'john')
user.posts.exists?(published: true)

# good
User.where(name: 'john').exists?
User.where(['name = ?', 'john']).exists?
User.where('name = ?', 'john').exists?
user.posts.where(published: true).exists?
User.where('length(name) > 10').exists?

Constants

MSG
RESTRICT_ON_SEND

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 65
def on_send(node)
  find_offenses(node) do |args|
    return unless convertable_args?(args)

    range = correction_range(node)
    good_method = build_good_method(args)
    message = format(MSG, good_method: good_method, bad_method: range.source)

    add_offense(range, message: message) do |corrector|
      corrector.replace(range, good_method)
    end
  end
end

Private Instance Methods

build_good_method(args) click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 111
def build_good_method(args)
  if exists_style?
    build_good_method_exists(args)
  elsif where_style?
    build_good_method_where(args)
  end
end
build_good_method_exists(args) click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 119
def build_good_method_exists(args)
  if args.size > 1
    "exists?([#{args.map(&:source).join(', ')}])"
  else
    "exists?(#{args[0].source})"
  end
end
build_good_method_where(args) click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 127
def build_good_method_where(args)
  if args.size > 1
    "where(#{args.map(&:source).join(', ')}).exists?"
  else
    "where(#{args[0].source}).exists?"
  end
end
convertable_args?(args) click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 97
def convertable_args?(args)
  return false if args.empty?

  args.size > 1 || args[0].hash_type? || args[0].array_type?
end
correction_range(node) click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 103
def correction_range(node)
  if exists_style?
    node.receiver.loc.selector.join(node.loc.selector)
  elsif where_style?
    node.loc.selector.with(end_pos: node.loc.expression.end_pos)
  end
end
exists_style?() click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 85
def exists_style?
  style == :exists
end
find_offenses(node, &block) click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 89
def find_offenses(node, &block)
  if exists_style?
    where_exists_call?(node, &block)
  elsif where_style?
    exists_with_args?(node, &block)
  end
end
where_style?() click to toggle source
# File lib/rubocop/cop/rails/where_exists.rb, line 81
def where_style?
  style == :where
end