Class: RuboCop::Cop::DevDoc::Style::PreferPublicSend

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/dev_doc/style/prefer_public_send.rb

Overview

Prefer public_send over send when dispatching dynamically.

Rationale

public_send respects Ruby's method visibility. When the dispatched name should resolve to a public API method (the common case for generic validators, form helpers, attribute readers, etc.), using public_send:

  • Surfaces typos / misconfig immediately. A name that drifts to a private internal raises NoMethodError instead of silently invoking it.
  • Future-proofs the call site. If the target method later moves to private (refactor, contract narrowing), public_send raises on the next run; send keeps silently working — potentially masking a real bug.
  • Documents intent. A reader of public_send knows the author meant a public-API call. send leaves it ambiguous whether private access was wanted.

Relationship to AvoidSend

DevDoc/Style/AvoidSend flags dynamic dispatch (both send and public_send) as risky. This cop is orthogonal: it flags send specifically, including the cases AvoidSend exempts (literal-symbol args, prefix-string args). The friction asymmetry is intentional — send is the deeper exception, so it costs an extra disable:

obj.public_send(method_name)        # AvoidSend: 1 disable
obj.public_send(:foo)               # clean
obj.send(method_name)               # AvoidSend + this cop: 2 disables
obj.send(:foo)                      # this cop only: 1 disable

When send IS the right choice

Reach for send only when you have an articulable reason to bypass visibility — and leave a comment so the next reader knows it's deliberate:

  • Tests calling a private helper:

    # rubocop:disable DevDoc/Style/PreferPublicSend -- unit-testing private method
    assert_equal 5, calculator.send(:internal_carry)
    # rubocop:enable DevDoc/Style/PreferPublicSend
    
  • Framework / introspection code that intentionally calls private callbacks (e.g. record.send(:_run_save_callbacks)).

Receiver-less send is exempt

send(:foo) (no explicit receiver) calls a method on self and is commonly used inside a class to dispatch to its own private methods. Rewriting to public_send would either change semantics (the method must become public) or fail. Same exemption as AvoidSend.

Examples:

# bad — prefer public_send
record.send(attribute)
instance.send(:public_method)
obj.send("export_#{x}")

# good — visibility-respecting dispatch
record.public_send(attribute)
instance.public_send(:public_method)
obj.public_send("export_#{x}")

# good — receiver-less send (calling self's own method)
send(:internal_helper)

Constant Summary collapse

MSG =
'Prefer `public_send` over `send` — it respects method ' \
'visibility, surfacing typos/misconfig that would otherwise ' \
'silently invoke a private method. Disable with a reason ' \
'when bypassing visibility is intentional.'.freeze
RESTRICT_ON_SEND =
%i[send].freeze

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



77
78
79
80
81
# File 'lib/rubocop/cop/dev_doc/style/prefer_public_send.rb', line 77

def on_send(node)
  return if node.receiver.nil?

  add_offense(node.loc.selector)
end