Class: RuboCop::Cop::Rails::SaveBang

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
NegativeConditional
Defined in:
lib/rubocop/cop/rails/save_bang.rb

Overview

Identifies possible cases where Active Record save! or related should be used instead of save because the model might have failed to save and an exception is better than unhandled failure.

This will allow:

  • update or save calls, assigned to a variable, or used as a condition in an if/unless/case statement.

  • create calls, assigned to a variable that then has a call to ‘persisted?`, or whose return value is checked by `persisted?` immediately

  • calls if the result is explicitly returned from methods and blocks, or provided as arguments.

  • calls whose signature doesn’t look like an ActiveRecord persistence method.

By default it will also allow implicit returns from methods and blocks. that behavior can be turned off with ‘AllowImplicitReturn: false`.

You can permit receivers that are giving false positives with ‘AllowedReceivers: []`

Examples:


# bad
user.save
user.update(name: 'Joe')
user.find_or_create_by(name: 'Joe')
user.destroy

# good
unless user.save
  # ...
end
user.save!
user.update!(name: 'Joe')
user.find_or_create_by!(name: 'Joe')
user.destroy!

user = User.find_or_create_by(name: 'Joe')
unless user.persisted?
  # ...
end

def save_user
  return user.save
end

AllowImplicitReturn: true (default)


# good
users.each { |u| u.save }

def save_user
  user.save
end

AllowImplicitReturn: false


# bad
users.each { |u| u.save }
def save_user
  user.save
end

# good
users.each { |u| u.save! }

def save_user
  user.save!
end

def save_user
  return user.save
end

AllowedReceivers: [‘merchant.customers’, ‘Service::Mailer’]


# bad
merchant.create
customers.builder.save
Mailer.create

module Service::Mailer
  self.create
end

# good
merchant.customers.create
MerchantService.merchant.customers.destroy
Service::Mailer.update(message: 'Message')
::Service::Mailer.update
Services::Service::Mailer.update(message: 'Message')
Service::Mailer::update

Constant Summary collapse

MSG =
'Use `%<prefer>s` instead of `%<current>s` if the return value is not checked.'
CREATE_MSG =
"#{MSG} Or check `persisted?` on model returned from `%<current>s`."
CREATE_CONDITIONAL_MSG =
'`%<current>s` returns a model which is always truthy.'
CREATE_PERSIST_METHODS =
%i[create create_or_find_by first_or_create find_or_create_by].freeze
MODIFY_PERSIST_METHODS =
%i[save update update_attributes destroy].freeze
RESTRICT_ON_SEND =
(CREATE_PERSIST_METHODS + MODIFY_PERSIST_METHODS).freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.joining_forcesObject



132
133
134
# File 'lib/rubocop/cop/rails/save_bang.rb', line 132

def self.joining_forces
  VariableForce
end

Instance Method Details

#after_leaving_scope(scope, _variable_table) ⇒ Object



136
137
138
139
140
141
142
# File 'lib/rubocop/cop/rails/save_bang.rb', line 136

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

#check_assignment(assignment) ⇒ Object



144
145
146
147
148
149
150
151
152
# File 'lib/rubocop/cop/rails/save_bang.rb', line 144

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

#on_send(node) ⇒ Object Also known as: on_csend

rubocop:disable Metrics/CyclomaticComplexity



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/rubocop/cop/rails/save_bang.rb', line 155

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