class RuboCop::Cop::Rails::SaveBang


Service::Mailer::update
Services::Service::Mailer.update(message: ‘Message’)
::Service::Mailer.update
Service::Mailer.update(message: ‘Message’)
MerchantService.merchant.customers.destroy
merchant.customers.create
# good
end
self.create
module Service::Mailer
Mailer.create
customers.builder.save
merchant.create
# bad
@example AllowedReceivers: [‘merchant.customers’, ‘Service::Mailer’]
end
return user.save
def save_user
end
user.save!
def save_user
users.each { |u| u.save! }
# good
end
user.save
def save_user
users.each { |u| u.save }
# bad
@example AllowImplicitReturn: false
end
user.save
def save_user
users.each { |u| u.save }
# good
@example AllowImplicitReturn: true (default)
end
return user.save
def save_user
end
# …
unless user.persisted?
user = User.find_or_create_by(name: ‘Joe’)
user.destroy!
user.find_or_create_by!(name: ‘Joe’)
user.update!(name: ‘Joe’)
user.save!
end
# …
unless user.save
# good
user.destroy
user.find_or_create_by(name: ‘Joe’)
user.update(name: ‘Joe’)
user.save
# bad
@example
—-
update
end
def update_attributes
# After running rubocop –safe-autocorrect
update_attributes
end
def update_attributes
# Original code
—-
[source,ruby]

but the method name in the definition would be unchanged.
This cop’s autocorrection is unsafe because a custom ‘update` method call would be changed to `update!`,
@safety
`AllowedReceivers: []`
You can permit receivers that are giving false positives with
that behavior can be turned off with `AllowImplicitReturn: false`.
By default it will also allow implicit returns from methods and blocks.
persistence method.
* calls whose signature doesn’t look like an ActiveRecord
or provided as arguments.
* calls if the result is explicitly returned from methods and blocks,
‘persisted?` immediately
call to `persisted?`, or whose return value is checked by
* create calls, assigned to a variable that then has a
or used as a condition in an if/unless/case statement.
* update or save calls, assigned to a variable,
This will allow:
save and an exception is better than unhandled failure.
should be used instead of save because the model might have failed to
Identifies possible cases where Active Record save! or related

def self.joining_forces

def self.joining_forces
  VariableForce
end

def after_leaving_scope(scope, _variable_table)

def after_leaving_scope(scope, _variable_table)
  scope.variables.each_value do |variable|
    variable.assignments.each do |assignment|
      check_assignment(assignment)
    end
  end
end

def allowed_receiver?(node)

def allowed_receiver?(node)
  return false unless node.receiver
  return true if node.receiver.const_name == 'ENV'
  return false unless cop_config['AllowedReceivers']
  cop_config['AllowedReceivers'].any? do |allowed_receiver|
    receiver_chain_matches?(node, allowed_receiver)
  end
end

def argument?(node)

def argument?(node)
  assignable_node(node).argument?
end

def array_parent(node)

def array_parent(node)
  array = node.parent
  return unless array&.array_type?
  array
end

def assignable_node(node)

def assignable_node(node)
  assignable = node.block_node || node
  while node
    node = hash_parent(node) || array_parent(node)
    assignable = node if node
  end
  assignable
end

def call_to_persisted?(node)

def call_to_persisted?(node)
  node = node.parent.condition if node.parenthesized_call? && node.parent.if_type?
  node.send_type? && node.method?(:persisted?)
end

def check_assignment(assignment)

def check_assignment(assignment)
  node = right_assignment_node(assignment)
  return unless node&.send_type?
  return unless persist_method?(node, CREATE_PERSIST_METHODS)
  return if persisted_referenced?(assignment)
  register_offense(node, CREATE_MSG)
end

def check_used_in_condition_or_compound_boolean(node)

def check_used_in_condition_or_compound_boolean(node)
  return false unless in_condition_or_compound_boolean?(node)
  register_offense(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
  true
end

def checked_immediately?(node)

def checked_immediately?(node)
  node.parent && call_to_persisted?(node.parent)
end

def conditional?(parent)

def conditional?(parent)
  parent.type?(:if, :case)
end

def const_matches?(const, allowed_const)

Const != NameSpace::Const
NameSpace::Const != ::Const
NameSpace::Const == NameSpace::Const
NameSpace::Const == Const
Const == ::Const
::Const == Const
::Const == ::Const
Const == Const
def const_matches?(const, allowed_const)
  parts = allowed_const.split('::').reverse.zip(const.split('::').reverse)
  parts.all? do |(allowed_part, const_part)|
    allowed_part == const_part.to_s
  end
end

def deparenthesize(node)

def deparenthesize(node)
  node = node.children.last while node.begin_type?
  node
end

def expected_signature?(node)

Check argument signature as no arguments or one hash
def expected_signature?(node)
  return true unless node.arguments?
  return false if !node.arguments.one? || node.method?(:destroy)
  node.first_argument.hash_type? || !node.first_argument.literal?
end

def explicit_return?(node)

def explicit_return?(node)
  ret = assignable_node(node).parent
  ret&.type?(:return, :next)
end

def find_method_with_sibling_index(node, sibling_index = 1)

def find_method_with_sibling_index(node, sibling_index = 1)
  return node, sibling_index unless node&.or_type?
  sibling_index += 1
  find_method_with_sibling_index(node.parent, sibling_index)
end

def hash_parent(node)

def hash_parent(node)
  pair = node.parent
  return unless pair&.pair_type?
  hash = pair.parent
  return unless hash&.hash_type?
  hash
end

def implicit_return?(node)

def implicit_return?(node)
  return false unless cop_config['AllowImplicitReturn']
  node = assignable_node(node)
  method, sibling_index = find_method_with_sibling_index(node.parent)
  return false unless method&.type?(:def, :any_block)
  method.children.size == node.sibling_index + sibling_index
end

def in_condition_or_compound_boolean?(node)

def in_condition_or_compound_boolean?(node)
  node = node.block_node || node
  parent = node.each_ancestor.find { |ancestor| !ancestor.begin_type? }
  return false unless parent
  operator_or_single_negative?(parent) || (conditional?(parent) && node == deparenthesize(parent.condition))
end

def on_send(node)

rubocop:disable Metrics/CyclomaticComplexity
def on_send(node)
  return unless persist_method?(node)
  return if return_value_assigned?(node)
  return if implicit_return?(node)
  return if check_used_in_condition_or_compound_boolean(node)
  return if argument?(node)
  return if explicit_return?(node)
  return if checked_immediately?(node)
  register_offense(node, MSG)
end

def operator_or_single_negative?(node)

def operator_or_single_negative?(node)
  node.operator_keyword? || single_negative?(node)
end

def persist_method?(node, methods = RESTRICT_ON_SEND)

def persist_method?(node, methods = RESTRICT_ON_SEND)
  methods.include?(node.method_name) && expected_signature?(node) && !allowed_receiver?(node)
end

def persisted_referenced?(assignment)

def persisted_referenced?(assignment)
  return false unless assignment.referenced?
  assignment.variable.references.any? do |reference|
    call_to_persisted?(reference.node.parent)
  end
end

def receiver_chain_matches?(node, allowed_receiver)

def receiver_chain_matches?(node, allowed_receiver)
  allowed_receiver.split('.').reverse.all? do |receiver_part|
    node = node.receiver
    return false unless node
    if node.variable?
      node.node_parts.first == receiver_part.to_sym
    elsif node.send_type?
      node.method?(receiver_part.to_sym)
    elsif node.const_type?
      const_matches?(node.const_name, receiver_part)
    end
  end
end

def register_offense(node, msg)

def register_offense(node, msg)
  current_method = node.method_name
  bang_method = "#{current_method}!"
  full_message = format(msg, prefer: bang_method, current: current_method)
  range = node.loc.selector
  add_offense(range, message: full_message) do |corrector|
    corrector.replace(range, bang_method)
  end
end

def return_value_assigned?(node)

def return_value_assigned?(node)
  return false unless (assignment = assignable_node(node).parent)
  assignment.assignment?
end

def right_assignment_node(assignment)

def right_assignment_node(assignment)
  node = assignment.node.child_nodes.first
  return node unless node&.any_block_type?
  node.send_node
end