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