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

Inherits:
Cop
  • Object
show all
Includes:
NegativeConditional
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 `%<prefer>s` instead of `%<current>s` if the return ' \
'value is not checked.'.freeze
CREATE_MSG =
(MSG +
' Or check `persisted?` on model returned from ' \
'`%<current>s`.').freeze
CREATE_CONDITIONAL_MSG =
'`%<method>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::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 included from NodePattern::Macros

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

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 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?, escape_string, first_part_of_call_chain, interpret_string_escapes, line_range, needs_escaping?, on_node, operator?, parentheses?, same_line?, to_string_literal, to_supported_styles, tokens

Methods included from PathUtil

absolute?, 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



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

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



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

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



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

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,
                                    prefer: "#{node.method_name}!",
                                    current: node.method_name.to_s))
end

#join_force?(force_class) ⇒ Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/rubocop/cop/rails/save_bang.rb', line 56

def join_force?(force_class)
  force_class == VariableForce
end

#on_send(node) ⇒ Object



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

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,
                                    prefer: "#{node.method_name}!",
                                    current: node.method_name.to_s))
end