class RailsBestPractices::Reviews::UseQueryAttributeReview

Make sure to use query attribute instead of nil?, blank? and present?.

See the best practice details here rails-bestpractices.com/posts/2010/10/03/use-query-attribute/

Implementation:

Review process:

check all method calls within conditional statements, like @user.login.nil?
if their receivers are one of the model names
and their messages of first call are not pluralize and not in any of the association names
and their messages of second call are one of nil?, blank?, present?, or they are == ""
then you can use query attribute instead.

Constants

QUERY_METHODS

Private Instance Methods

compare_with_empty_string?(node) click to toggle source

check if the node is with node type :binary, node message :== and node argument is empty string.

# File lib/rails_best_practices/reviews/use_query_attribute_review.rb, line 113
def compare_with_empty_string?(node)
  node.sexp_type == :binary && ['==', '!='].include?(node.message.to_s) &&
    s(:string_literal, s(:string_content)) == node.argument
end
is_model?(variable_node) click to toggle source

check if the receiver is one of the models.

# File lib/rails_best_practices/reviews/use_query_attribute_review.rb, line 97
def is_model?(variable_node)
  return false if variable_node.const?

  class_name = variable_node.to_s.sub(/^@/, '').classify
  models.include?(class_name)
end
model_attribute?(variable_node, message) click to toggle source

check if the receiver and message is one of the model's attribute. the receiver should match one of the class model name, and the message should match one of attribute name.

# File lib/rails_best_practices/reviews/use_query_attribute_review.rb, line 106
def model_attribute?(variable_node, message)
  class_name = variable_node.to_s.sub(/^@/, '').classify
  attribute_type = model_attributes.get_attribute_type(class_name, message)
  attribute_type && !%w[integer float].include?(attribute_type)
end
possible_query_attribute?(node) click to toggle source

check if the node may use query attribute instead.

if the node contains two method calls, e.g. @user.login.nil?

for the first call, the receiver should be one of the class names and the message should be one of the attribute name.

for the second call, the message should be one of nil?, blank? or present? or it is compared with an empty string.

the node that may use query attribute.

# File lib/rails_best_practices/reviews/use_query_attribute_review.rb, line 86
def possible_query_attribute?(node)
  return false unless node.receiver.sexp_type == :call

  variable_node = variable(node)
  message_node = node.grep_node(receiver: variable_node.to_s).message

  is_model?(variable_node) && model_attribute?(variable_node, message_node.to_s) &&
    (QUERY_METHODS.include?(node.message.to_s) || compare_with_empty_string?(node))
end
query_attribute_node(conditional_statement_node) click to toggle source

recursively check conditional statement nodes to see if there is a call node that may be possible query attribute.

# File lib/rails_best_practices/reviews/use_query_attribute_review.rb, line 57
def query_attribute_node(conditional_statement_node)
  case conditional_statement_node.sexp_type
  when :and, :or
    node =
      query_attribute_node(conditional_statement_node[1]) || query_attribute_node(conditional_statement_node[2])
    node.file = conditional_statement_code.file
    return node
  when :not
    node = query_attribute_node(conditional_statement_node[1])
    node.file = conditional_statement_node.file
  when :call
    return conditional_statement_node if possible_query_attribute?(conditional_statement_node)
  when :binary
    return conditional_statement_node if possible_query_attribute?(conditional_statement_node)
  end
  nil
end