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 =
%i[create
first_or_create find_or_create_by].freeze
MODIFY_PERSIST_METHODS =
%i[save
update update_attributes 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, autocorrect_incompatible_with, 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_rails_version, #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?, comment_line?, double_quotes_required?, effective_column, ends_its_line?, escape_string, first_part_of_call_chain, interpret_string_escapes, line_range, needs_escaping?, on_node, operator?, parentheses?, parenthesized_call?, precede?, range_between, range_by_whole_lines, range_with_surrounding_comma, range_with_surrounding_space, same_line?, source_range, strip_quotes, stripped_source_upto, symbol_without_quote?, to_string_literal, to_supported_styles, to_symbol_literal, within_node?

Methods included from PathUtil

absolute?, find_file_upwards, match_path?, pwd, relative_path, reset_pwd, smart_path

Constructor Details

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

Instance Method Details

#after_leaving_scope(scope, _variable_table) ⇒ Object



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

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

#autocorrect(node) ⇒ Object



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

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



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

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, location: :selector,
                    message: format(CREATE_MSG,
                                    "#{node.method_name}!",
                                    node.method_name.to_s,
                                    node.method_name.to_s))
end

#join_force?(force_class) ⇒ Boolean

Returns:

  • (Boolean)


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

def join_force?(force_class)
  force_class == VariableForce
end

#on_send(node) ⇒ Object



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

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, location: :selector,
                    message: format(MSG,
                                    "#{node.method_name}!",
                                    node.method_name.to_s))
end