class RuboCop::Cop::Rails::WhereMissing
Post.where.missing(:author)
# good
Post.left_joins(:author).where(authors: { id: nil })
# bad
@example
This cop is enabled in Rails 6.1 or higher.
Use ‘where.missing(…)` to find missing relationship records.
def message(node, where_argument)
def message(node, where_argument) format(MSG, left_joins_association: node.first_argument.value, left_joins_method: node.method_name, where_association: where_argument.value) end
def multi_condition?(where_arg)
def multi_condition?(where_arg) where_arg.children.count > 1 end
def on_send(node)
def on_send(node) return unless node.first_argument&.sym_type? root_receiver = root_receiver(node) where_node_and_argument(root_receiver) do |where_node, where_argument| next unless root_receiver == root_receiver(where_node) next unless same_relationship?(where_argument, node.first_argument) range = range_between(node.loc.selector.begin_pos, node.source_range.end_pos) register_offense(node, where_node, where_argument, range) break end end
def register_offense(node, where_node, where_argument, range)
def register_offense(node, where_node, where_argument, range) add_offense(range, message: message(node, where_argument)) do |corrector| corrector.replace(node.loc.selector, 'where.missing') if multi_condition?(where_node.first_argument) replace_where_method(corrector, where_node) else remove_where_method(corrector, node, where_node) end end end
def remove_where_method(corrector, node, where_node)
def remove_where_method(corrector, node, where_node) range = range_between(where_node.loc.selector.begin_pos, where_node.loc.end.end_pos) if node.multiline? && !same_line?(node, where_node) range = range_by_whole_lines(range, include_final_newline: true) elsif where_node.receiver corrector.remove(where_node.loc.dot) else corrector.remove(node.loc.dot) end corrector.remove(range) end
def replace_range(child)
def replace_range(child) if (right_sibling = child.right_sibling) range_between(child.source_range.begin_pos, right_sibling.source_range.begin_pos) else range_between(child.left_sibling.source_range.end_pos, child.source_range.end_pos) end end
def replace_where_method(corrector, where_node)
def replace_where_method(corrector, where_node) missing_relationship(where_node) do |where_clause| corrector.remove(replace_range(where_clause)) end end
def root_receiver(node)
def root_receiver(node) parent = node.parent if !parent&.send_type? || parent.method?(:or) || parent.method?(:and) node else root_receiver(parent) end end
def same_line?(left_joins_node, where_node)
def same_line?(left_joins_node, where_node) left_joins_node.loc.selector.line == where_node.loc.selector.line end
def same_relationship?(where, left_joins)
def same_relationship?(where, left_joins) where.value.to_s.match?(/^#{left_joins.value}s?$/) end