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

Inherits:
Cop
  • Object
show all
Defined in:
lib/rubocop/cop/rails/save_bang.rb

Overview

This cop 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 ignore calls that return a boolean for success if the result is assigned to a variable or used as the condition in an if/unless statement. It will also ignore calls that return a model assigned to a variable that has a call to ‘persisted?`. Finally, it will ignore any call with more than 2 arguments as that is likely not an Active Record call or a Model.update(id, attributes) call.

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

Constant Summary collapse

MSG =
'Use `%s` instead of `%s` if the return value is not checked.'
.freeze
CREATE_MSG =
(MSG +
 ' Or check `persisted?` on model returned from `%s`.')
.freeze
CREATE_CONDITIONAL_MSG =
'`%s` returns a model which is always truthy.'
.freeze
CREATE_PERSIST_METHODS =
[:create,
:first_or_create, :find_or_create_by].freeze
MODIFY_PERSIST_METHODS =
[:save, :update, :destroy].freeze
PERSIST_METHODS =
(CREATE_PERSIST_METHODS +
MODIFY_PERSIST_METHODS).freeze

Constants included from Util

Util::ASGN_NODES, Util::BYTE_ORDER_MARK, Util::CONDITIONAL_NODES, Util::EQUALS_ASGN_NODES, Util::LITERAL_REGEX, Util::LOGICAL_OPERATOR_NODES, Util::MODIFIER_NODES, Util::OPERATOR_METHODS, Util::SHORTHAND_ASGN_NODES

Instance Attribute Summary

Attributes inherited from Cop

#config, #corrections, #offenses, #processed_source

Instance Method Summary collapse

Methods inherited from Cop

#add_offense, all, badge, #config_to_allow_offenses, #config_to_allow_offenses=, #cop_config, #cop_name, cop_name, #correct, department, #duplicate_location?, #excluded_file?, #find_location, #highlights, inherited, #initialize, lint?, match?, #message, #messages, non_rails, #parse, qualified_cop_name, #relevant_file?, #target_ruby_version

Methods included from AST::Sexp

#s

Methods included from NodePattern::Macros

#def_node_matcher, #def_node_search, #node_search, #node_search_all, #node_search_body, #node_search_first

Methods included from AutocorrectLogic

#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #support_autocorrect?

Methods included from IgnoredNode

#ignore_node, #ignored_node?, #part_of_ignored_node?

Methods included from Util

begins_its_line?, block_length, comment_line?, compatible_external_encoding_for?, directions, double_quotes_acceptable?, double_quotes_required?, effective_column, ends_its_line?, escape_string, first_part_of_call_chain, hard_to_type?, interpret_string_escapes, line_distance, line_range, move_pos, needs_escaping?, numeric_range_size, on_node, operator?, parentheses?, parenthesized_call?, preceed?, range_between, range_with_surrounding_comma, range_with_surrounding_space, same_line?, source_range, strip_quotes, stripped_source_upto, to_string_literal, to_supported_styles, to_symbol_literal, within_node?

Methods included from PathUtil

absolute?, match_path?, relative_path

Constructor Details

This class inherits a constructor from RuboCop::Cop::Cop

Instance Method Details

#after_leaving_scope(scope, _variable_table) ⇒ Object



57
58
59
60
61
62
63
# File 'lib/rubocop/cop/rails/save_bang.rb', line 57

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

#autocorrect(node) ⇒ Object



92
93
94
95
96
97
# File 'lib/rubocop/cop/rails/save_bang.rb', line 92

def autocorrect(node)
  save_loc = node.loc.selector
  new_method = "#{node.method_name}!"

  ->(corrector) { corrector.replace(save_loc, new_method) }
end

#check_assignment(assignment) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rubocop/cop/rails/save_bang.rb', line 65

def check_assignment(assignment)
  node = right_assignment_node(assignment)
  return unless node
  return unless CREATE_PERSIST_METHODS.include?(node.method_name)
  return unless expected_signature?(node)
  return if persisted_referenced?(assignment)

  add_offense(node, node.loc.selector,
              format(CREATE_MSG,
                     "#{node.method_name}!",
                     node.method_name.to_s,
                     node.method_name.to_s))
end

#join_force?(force_class) ⇒ Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/rubocop/cop/rails/save_bang.rb', line 53

def join_force?(force_class)
  force_class == VariableForce
end

#on_send(node) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/rubocop/cop/rails/save_bang.rb', line 79

def on_send(node)
  return unless PERSIST_METHODS.include?(node.method_name)
  return unless expected_signature?(node)
  return if return_value_assigned?(node)
  return if check_used_in_conditional(node)
  return if last_call_of_method?(node)

  add_offense(node, node.loc.selector,
              format(MSG,
                     "#{node.method_name}!",
                     node.method_name.to_s))
end