class RuboCop::Cop::Rails::FindByOrAssignmentMemoization
end
@current_user.do_something
end
@current_user = User.find_by(id: session)
else
@current_user
if defined?(@current_user)
def current_user
# good
end
@current_user.do_something
@current_user ||= User.find_by(id: session)
def current_user
# bad - method contains other code
end
@current_user = User.find_by(id: session)
return @current_user if defined?(@current_user)
def current_user
# good
end
@current_user ||= User.find_by(id: session)
def current_user
# bad - exclusively doing memoization
@example
or the code may have a different purpose than memoization.
This cop is unsafe because detected ‘find_by` may not be Active Record’s method,
@safety
for memoization that are initialized at object creation are ignored.
NOTE: Respecting the object shapes introduced in Ruby 3.2, instance variables used
but ‘find_by` may return `nil`, in which case it is not memoized as intended.
It is common to see code that attempts to memoize `find_by` result by `||=`,
Avoid memoizing `find_by` results with `||=`.
def correct_to_regular_method_definition(corrector, node)
def correct_to_regular_method_definition(corrector, node) range = node.loc.assignment.join(node.body.source_range.begin) corrector.replace(range, "\n") corrector.insert_after(node, "\nend") end
def initialize_methods
def initialize_methods @initialize_methods ||= processed_source.ast.each_descendant(:def).select { |node| node.method?(:initialize) } end
def instance_variable_assigned?(instance_variable_name)
def instance_variable_assigned?(instance_variable_name) initialize_methods.any? do |def_node| def_node.each_descendant(:ivasgn).any? do |asgn_node| asgn_node.name == instance_variable_name end end end
def on_def(node)
def on_def(node) find_by_or_assignment_memoization(node.body) do |variable_name, find_by| next if instance_variable_assigned?(variable_name) add_offense(node.body) do |corrector| corrector.replace( node.body, <<~RUBY.rstrip return #{variable_name} if defined?(#{variable_name}) #{variable_name} = #{find_by.source} RUBY ) correct_to_regular_method_definition(corrector, node) if node.endless? end end end
def on_send(node)
def on_send(node) assignment_node = node.parent find_by_or_assignment_memoization(assignment_node) do |variable_name, find_by| next if assignment_node.each_ancestor(:if).any? || instance_variable_assigned?(variable_name) add_offense(assignment_node) do |corrector| corrector.replace( assignment_node, <<~RUBY.rstrip if defined?(#{variable_name}) #{variable_name} else #{variable_name} = #{find_by.source} end RUBY ) end end end