class RuboCop::Cop::Rails::WhereRange
User.where(‘age > ?’, 18)
# There are no beginless ranges in ruby.
# good
User.where(users: { age: 18.. })
User.where(age: 18…21)
User.where(age: …18)
User.where.not(age: 18..)
User.where(age: 18..)
# good
User.where(‘users.age >= ?’, 18)
User.where(‘age >= :start’, start: 18)
User.where(‘age >= ? AND age < ?’, 18, 21)
User.where(‘age < ?’, 18)
User.where.not(‘age >= ?’, 18)
User.where(‘age >= ?’, 18)
# bad
@example
explicitly attached to the ‘bookings` table.
`Booking.joins(:events).where(end_at: …Time.current)`, it will now be incorrectly
implicitly attach the `end_at` column to the `events` table. But when autocorrected to
For example, `Booking.joins(:events).where(’end_at < ?‘, Time.current)` will correctly
by explicitly attaching the column to the wrong table.
This cop’s autocorrection is unsafe because it can change the query
@safety
in ‘where` can be replaced with ranges.
Identifies places where manually constructed SQL
def build_good_method(method_name, column, value)
def build_good_method(method_name, column, value) if column.include?('.') table, column = column.split('.') "#{method_name}(#{table}: { #{column}: #{value} })" else "#{method_name}(#{column}: #{value})" end end
def extract_column_and_value(template_node, values_node)
def extract_column_and_value(template_node, values_node) case template_node.value when GTEQ_ANONYMOUS_RE lhs = values_node[0] operator = '..' when LTEQ_ANONYMOUS_RE if target_ruby_version >= 2.7 operator = range_operator(Regexp.last_match(2)) rhs = values_node[0] end when RANGE_ANONYMOUS_RE if values_node.size >= 2 lhs = values_node[0] operator = range_operator(Regexp.last_match(2)) rhs = values_node[1] end when GTEQ_NAMED_RE value_node = values_node[0] if value_node.hash_type? pair = find_pair(value_node, Regexp.last_match(2)) lhs = pair.value operator = '..' end when LTEQ_NAMED_RE value_node = values_node[0] if value_node.hash_type? pair = find_pair(value_node, Regexp.last_match(2)) if pair && target_ruby_version >= 2.7 operator = range_operator(Regexp.last_match(2)) rhs = pair.value end end when RANGE_NAMED_RE value_node = values_node[0] if value_node.hash_type? pair1 = find_pair(value_node, Regexp.last_match(2)) pair2 = find_pair(value_node, Regexp.last_match(4)) if pair1 && pair2 lhs = pair1.value operator = range_operator(Regexp.last_match(3)) rhs = pair2.value end end else return end if lhs lhs_source = parentheses_needed?(lhs) ? "(#{lhs.source})" : lhs.source end if rhs rhs_source = parentheses_needed?(rhs) ? "(#{rhs.source})" : rhs.source end column_qualifier = Regexp.last_match(1) return if column_qualifier.count('.') > 1 [column_qualifier, "#{lhs_source}#{operator}#{rhs_source}"] if operator end
def find_pair(hash_node, value)
def find_pair(hash_node, value) hash_node.pairs.find { |pair| pair.key.value.to_sym == value.to_sym } end
def offense_range(node)
def offense_range(node) range_between(node.loc.selector.begin_pos, node.source_range.end_pos) end
def on_send(node)
def on_send(node) return if node.method?(:not) && !where_not?(node) where_range_call?(node) do |template_node, values_node| column, value = extract_column_and_value(template_node, values_node) return unless column range = offense_range(node) good_method = build_good_method(node.method_name, column, value) message = format(MSG, good_method: good_method) add_offense(range, message: message) do |corrector| corrector.replace(range, good_method) end end end
def parentheses_needed?(node)
def parentheses_needed?(node) !parentheses_not_needed?(node) end
def parentheses_not_needed?(node)
def parentheses_not_needed?(node) node.variable? || node.literal? || node.reference? || node.const_type? || node.begin_type? || parenthesized_call_node?(node) end
def parenthesized_call_node?(node)
def parenthesized_call_node?(node) node.call_type? && (node.arguments.empty? || node.parenthesized_call?) end
def range_operator(comparison_operator)
def range_operator(comparison_operator) comparison_operator == '<' ? '...' : '..' end
def where_not?(node)
def where_not?(node) receiver = node.receiver receiver&.send_type? && receiver.method?(:where) end