Class: RuboCop::Cop::DevDoc::Style::PreferPublicSend
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Style::PreferPublicSend
- 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
NoMethodErrorinstead of silently invoking it. - Future-proofs the call site. If the target method later moves
to private (refactor, contract narrowing),
public_sendraises on the next run;sendkeeps silently working — potentially masking a real bug. - Documents intent. A reader of
public_sendknows the author meant a public-API call.sendleaves 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.
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 |