From: "matthewd (Matthew Draper)" Date: 2022-02-14T15:02:08+00:00 Subject: [ruby-core:107579] [Ruby master Feature#12962] Feature Proposal: Extend 'protected' to support module friendship Issue #12962 has been updated by matthewd (Matthew Draper). > Calling "private/protected" methods on another object feels rather wrong to me. The whole point of `protected` is that it allows you to call methods on another object. But I assume you meant another object that is not an instance of the caller's class. A quick grep for `:nodoc:` turned up this example: https://github.com/ruby/ruby/blob/26187a8520b8c6645206a2064c11a7ab86a89845/lib/net/http/response.rb#L163 In my experience it's just not unusual for two collaborating objects to need to talk to each other in more detail than their "user"-facing public API intends to expose. In a typed OO language, this is where the objects communicate using a private concrete class, while only "publishing" a more focused Interface. As in that Net::HTTP example, a separate module to stash the methods is not really viable, because we're talking about instance methods that need access to ivars -- that's why it's a method on the current target object, and not a private method on the caller. ---------------------------------------- Feature #12962: Feature Proposal: Extend 'protected' to support module friendship https://bugs.ruby-lang.org/issues/12962#change-96490 * Author: matthewd (Matthew Draper) * Status: Open * Priority: Normal ---------------------------------------- When working on a larger library, with many classes that have both official API methods and internal supporting methods, it can be hard to distinguish between them. In Rails, for example, we currently do this using `:nodoc:` -- if a method is hidden from the documentation, it is not part of the officially supported API, even if it has `public` visibility. This approach can be confusing for users, however, because they can find methods that seem to do what they want, and start calling them, without ever looking at the documentation: either by just guessing a likely method name, or even being guided to it by `did_you_mean`. Method visibility controls seem like the right solution to this problem: if we make the methods `private` or `protected`, users can still choose to call them, but only by first acknowledging that they're using internal API. However, as we have object oriented internals, a lot of our internal API calls are between instances of unrelated classes... and using `send` on all those calls would make our own code very untidy. I propose that the solution to this problem is to make `protected` more widely useful, by allowing a module to nominate other modules that are allowed to call its protected methods. ```ruby class A protected def foo "secrets" end end class D def call_foo A.new.foo end end A.friend D D.new.call_foo # => "secrets" ``` This change is backwards compatible for existing uses of `protected`: a module is always considered its own friend (so calls that previously worked will continue to do so), and classes have no other friends by default (so calls that were previously disallowed will also continue to do so). Using a module, a library can easily establish a 'friendship group' of related classes without needing to link them individually, as well as providing a single opt-in for user code that consciously chooses to use unsupported APIs. ```ruby module MyLib module Internals end class A include Internals friend Internals protected def foo "implementation" end end class B include Internals friend Internals protected def bar A.new.foo end end end class UserCode def call_things [MyLib::A.new.foo, MyLib::B.new.bar] end end class FriendlyUserCode include MyLib::Internals def call_things [MyLib::A.new.foo, MyLib::B.new.bar] end end UserCode.new.call_things # !> NoMethodError: protected method `foo'.. FriendlyUserCode.new.call_things # => ["implementation", "implementation"] ``` This change seems in keeping with the ruby philosophy that a method's visibility is more of a guideline than a strictly enforced rule -- here, we allow the callee to blur the line, instead of leaving it up to the caller to use `send`. The implementation is surprisingly simple, and only adds time (searching an array of friends, instead of only looking for the current class) after a method call has already resolved to a protected method. While I'm personally most interested in how this could be applied in a Rails-sized project (such as.. Rails), I believe it would provide a helpful clarifying tool to any library that has multiple collaborating classes, whose instances are also exposed to user code. -- https://bugs.ruby-lang.org/ Unsubscribe: