Why can I access private/protected methods using Object#send in Ruby? - ruby

The class
class A
private
def foo
puts :foo
end
public
def bar
puts :bar
end
private
def zim
puts :zim
end
protected
def dib
puts :dib
end
end
instance of A
a = A.new
test
a.foo rescue puts :fail
a.bar rescue puts :fail
a.zim rescue puts :fail
a.dib rescue puts :fail
a.gaz rescue puts :fail
test output
fail
bar
fail
fail
fail
.send test
[:foo, :bar, :zim, :dib, :gaz].each { |m| a.send(m) rescue puts :fail }
.send output
foo
bar
zim
dib
fail
The question
The section labeled "Test Output" is the expected result. So why can I access private/protected method by simply Object#send?
Perhaps more important:
What is the difference between public/private/protected in Ruby? When to use each? Can someone provide real world examples for private and protected usage?

Technically: Because send doesn't do anything to check method visibility. (It would be more work to do so.)
Philosophically: Ruby is a very permissive language. You can already just open up a class and make any method you want public. The language designers implemented send in a way that allows it to override the restrictions normally imposed by private. Ruby 1.9 was originally going to have two variants, a private-respecting send and an unsafe variant called send!, but this was apparently dropped for backwards compatibility.
As for what private, protected and public mean:
public methods can be called by any sender
protected methods cannot be called outside of an instance of the method's class or an instance of a subclass
private methods cannot be called with an explicit receiver (with a couple of exceptions, such as setter methods, which always have to have an explicit receiver, and so can be called within the class that way)

Related

Determining method's visibility on the fly

I am writing a method that will define an instance method inside a class; something similar to attr_accessor:
class Foo
custom_method(:foo)
end
I have implemented that by adding custom_method function to the Module module, and defining the method with define_method, which works fine. But I cannot figure out how to take into account visibility attributes from the class. For example, in the following class
class Foo
custom_method(:foo)
private
custom_method(:bar)
end
the first generated method (foo) must be public, and the second one (bar) must be private. How do I do that? Or, how do I find the context in which my custom_method is called: private, public, or protected?
Thanks!
After experimenting with this for a bit, I'm completely baffled. Initially, I thought that Ruby took the default visibility (public, private, or protected) into account when you call Module#define_method. It turns out though that on Ruby versions <= 2.0, that's not the case:
class Foo
private
define_method :foo do
puts "Foo called!"
end
end
Foo.new.foo # Prints "Foo called!"
On Ruby 2.1+, it's even more confusing. Module#define_method seems to take default method visibility into account:
class Foo
private
define_method :foo do
puts "Foo called!"
end
end
Foo.new.foo # NoMethodError: private method `foo' called for #<Foo:0x8cb75ac>
But it only works when you are calling define_method from directly inside the class. Calling a method which then calls define_method doesn't work:
class Foo
def self.hello_on name
define_method name do
puts "Hello, #{name}!"
end
end
private
hello_on :foo
end
Foo.new.foo # Prints "Hello, foo!"
Dang it Ruby! Why?
Okay, this calls for desperate measures...
module DefaultMethodVisibilityAccessor
attr_reader :current_default_method_visibility
def public(*args)
#current_default_method_visibility = :public if args.empty?
super
end
def protected(*args)
#current_default_method_visibility = :protected if args.empty?
super
end
def private(*args)
#current_default_method_visibility = :private if args.empty?
super
end
end
class Module
prepend DefaultMethodVisibilityAccessor
end
module MethodDefiner
def hello_on name
define_method name do
puts "Hello, #{name}!"
end
case current_default_method_visibility
when :public
public name
when :protected
protected name
when :private
private name
end
end
end
Usage:
class Foo
extend MethodDefiner
hello_on :foo
private
hello_on :bar
end
Foo.new.foo # Prints "Hello, foo!"
Foo.new.bar # NoMethodError: private method `bar' called for #<Foo:0x8ec18fc>
There, fixed!
I think this is impossible, because the scope visibility level set by Module.private is managed at the C virtual machine level and not exposed to Ruby.
EDIT: and it's only available in the same syntactical scope that it is called, so when you call custom_method it loses the visibility level set inside the class declaration.
It's set in set_visibility(), and used in vm_define_method(), but I can't find any reference to the corresponding variable being available from Ruby.
I suggest using some kind of custom parameter to specify the visibility level of your methods.
You can use Module#private_method_defined? to verify if a method is defined as private

How to 'disable' a certain method in ruby from being called in the console

Let's say I have a class ABC with two methods.
class ABC
def test
"test"
end
def display_test
puts test
end
end
I only want to be able to call ABC.new.display_test from my console (IRB) (returning me 'test') and not be able to call ABC.new.test or ABC.new.send(:test) for that matter. Is this possible? If so, how?
The most thorough way to do so would be to have test private and override the send method to specifically block the call to test:
class ABC
def test
"test"
end
private :test
def display_test
puts test
end
def send(id)
if id == :test
raise NoMethodError.new("error")
else
super(id)
end
end
alias_method :__send__, :send
end
Please note that this send override is not a proper one, as it only accepts a single parameter.
If done the right way, it would become possible to do something like this:
ABC.new.send(:send, :test)
Make your method private.
One way to do this is:
class Foo
private
def bar
# …
end
end
Another way is:
class Foo
def bar
# …
end
private :bar
end
Note that this won't entirely prevent the method from getting called: it'll only stop "normal" calls. Indeed, there are many ways to call a private method from outside of a class in Ruby. To list a few off the top of my head:
Foo.new.send :bar
Foo.new.__send__ :bar
Foo.new.instance_eval { bar }
You could try to enumerate every possible way to send a message to a method, but your efforts will likely be fruitless in the end: Ruby is far too open for you to be able to seal the gates. So stick to making it private.
This is partly possible. You can remove it from being called directly, but not through send. This is the way that Ruby's private methods work.
To prevent it from being called directly, make it private.
private :test
class TestClass
def display_test
puts test
end
private
def test
"test"
end
end
This is a full example:
puts TestClass.new.display_test
puts TestClass.new.send(:test)
begin
puts TestClass.new.test
rescue
puts "Error!"
end
Ruby Private Methods
You can use undef method to do disable any methods, but in this case, you can't call it anywhere.
class ABC
def test
"test"
end
def display_test
puts test
end
undef :display_test
end

Are all methods public by default?

If I have this in my main program:
def hi
puts 'hi'
end
self.hi
it won't work because hi is private.
I've learned that all methods in Ruby are public by default, but this doesn't seem to be the case, why?
It is tricky to define a method in the main environment. A method is private by default when you define it in main. So you either have to use it as private:
def hi
puts 'hi'
end
hi
or explicitly make it public:
def hi
puts 'hi'
end
public :hi
self.hi
Methods are public by default, except for "top-level procedures". If you define something which looks like a top-level procedure, it will actually be defined as a private instance method of Object.
def main_method
p "This is from main_method"
end
public :main_method
class Klass
def initialize
Object.main_method # Or we can simply say main_method as we do in the second exaple
end
end
puts Klass.new
It is the way to create Object 'class methods' as shown. If it were private, then we could not specify the receiver.
When it is private, as it is by default, then we would call it like this:
def main_method
p "This is from main_method"
end
class Klass
def initialize
main_method # With it being private, implicit self only
end
end
puts Klass.new
So is there any advantage to either way? I guess if you make it private, you send some kind of communication to the developer that you have a preference and this should be used as a private method, rather than a public method.
In practice, I don't see an advantage.

How does one access protected class methods from instance methods in Ruby?

I must be missing something about how people do this in Ruby.
If '#protected' is uncommented we get:
in 'what': protected method 'zoop' called for Foo:Class (NoMethodError)
Is there a better way to approach protected class methods?
class Foo
class << self
#protected
def zoop
"zoop"
end
end
public
def what
"it is '#{self.class.zoop}'"
end
protected
end
a = Foo.new
p a.what # => "it is 'zoop'"
I would like zoop to be protected or private (no calling 'Foo.zoop'), but so far, I can't seem to find an elegant way.
It barely matters to make methods private or protected in Ruby, since you can just call send() to get around them.
If you want zoop to stay protected, use send() like this:
def what
"it is '#{self.class.send(:zoop)}'"
end
Upon further discussions with rue: and drbrain: in ruby-lang, it turns out that my impulse to save memory by placing utility functions at the class level was misplaced.
In Ruby, the instance methods hang off the class anyway, and the answer is to go ahead and place the utility functions at the instance level as private.
In summary, a utility function that is accessed only by instance methods:
class Foo
def what
"it is '#{zoop}'"
end
private
def zoop
"zoop"
end
end
p Foo.new.what # => "it is 'zoop'"
For a utility function that needs to be called from instance and class methods, a nested module seemed to be a popular approach:
class Foo
module Util
def self.zoop
"zoop"
end
end
def what
"it is '#{Util.zoop}'"
end
class << self
def class_what
"for all time it is '#{Util.zoop}'"
end
end
end
p Foo.new.what # => "it is 'zoop'"
p Foo.class_what # => "for all time it is 'zoop'"
p Foo::Util.zoop # visible, alas

Understanding private methods in Ruby

class Example
private
def example_test
puts 'Hello'
end
end
e = Example.new
e.example_test
This of course will not work, because we specified explicit receiver - instance of Example (e), and that is against a "private rule".
But I cannot understand, why one cannot do in Ruby this:
class Foo
def public_m
self.private_m # <=
end
private
def private_m
puts 'Hello'
end
end
Foo.new.public_m
The current object inside public_m method definition (i.e. self) is the instance of Foo. So why it is not allowed? To fix that I have to change self.private_m to just private_m. But why this differ, isn't the self an instance of Foo inside public_m? And who is the receiver of bare-word private_m call? Isn't that self - what actually you omit because, Ruby will do it for you (will call private_m on self)?
I hope I didn't confuse it too much, I am still fresh to Ruby.
EDIT:
Thank you for all the answers. Putting them all together I was able (finally) to grok the obvious (and not so obvious for someone, who have never seen things like Ruby): that self itself can be
explicit and implicit receiver and that make the difference. So there are two rules, if you want to call a private method: self must be implicit receiver, and that self must be an instance of current class (Example in that case - and that takes place only when self if inside instance method definition, during this method execution). Please correct me if I am wrong.
class Example
# self as an explicit receiver (will throw an error)
def explicit
self.some_private_method
end
# self as an implicit receiver (will be ok)
def implicit
some_private_method
end
private
def some_private_method; end
end
Example.new.implicit
Message for anyone who could find this question during the google trails: this may be helpful - http://weblog.jamisbuck.org/2007/2/23/method-visibility-in-ruby
Here's the short and the long of it. What private means in Ruby is a method cannot be called with an explicit receivers, e.g. some_instance.private_method(value). So even though the implicit receiver is self, in your example you explicitly use self so the private methods are not accessible.
Think of it this way, would you expect to be able to call a private method using a variable that you have assigned to an instance of a class? No. Self is a variable so it has to follow the same rules. However when you just call the method inside the instance then it works as expected because you aren't explicitly declaring the receiver.
Ruby being what it is you actually can call private methods using instance_eval:
class Foo
private
def bar(value)
puts "value = #{value}"
end
end
f = Foo.new
begin
f.bar("This won't work")
rescue Exception=>e
puts "That didn't work: #{e}"
end
f.instance_eval{ bar("But this does") }
Hope that's a little more clear.
-- edit --
I'm assuming you knew this will work:
class Foo
def public_m
private_m # Removed self.
end
private
def private_m
puts 'Hello'
end
end
Foo.new.public_m
The definition of private in Ruby is "can only be called without an explicit receiver". And that's why you can only call private methods without an explicit receiver. There is no other explanation.
Note that there actually is an exception to the rule: because of the ambiguity between local variables and method calls, the following will always be resolved to be an assignment to a local variable:
foo = :bar
So, what do you do if you want to call a writer called foo=? Well, you have to add an explicit receiver, because without the receiver Ruby simply won't know that you want to call the method foo= instead of assigning to the local variable foo:
self.foo = :bar
But what do you do if you want to call a private writer called foo=? You can't write self.foo = because foo= is private and thus cannot be called with an explicit receiver. Well, actually for this specific case (and this case alone), you can actually use an explicit receiver of self to call a private writer.
It's weird, but many things about Ruby's visibility modifiers are weird. Even if self is the implicit receiver, actually spelling it out makes it explicit in the eyes of the Ruby runtime. When it says that private methods cannot be called with an explicit receiver, that is what it means, even self counts.
IIRC, private methods allow only implicit receiver (which is always self, of course).
Sorry for my prevoius answer. I just don't understand your question.
I changed your code like this:
class Foo
def public_m
private_m # <=
end
def Foo.static_m
puts "static"
end
def self.static2_m
puts "static 2"
end
private
def private_m
puts 'Hello'
end
end
Foo.new.public_m
Foo.static_m
Foo.static2_m
Here is a call of instance method:
def public_m
private_m # <=
end
Here are a call of class methods:
def Foo.static_m
puts "static"
end
def self.static2_m
puts "static 2"
end
Foo.static_m
Foo.static2_m
Adding some enhancements to User Gates solution. Calling a private method to class method or an instance method is pretty much possible. Here is the Code Snippets. But not recommended.
Class Method
class Example
def public_m
Example.new.send(:private_m)
end
private
def private_m
puts 'Hello'
end
end
e = Example.new.public_m
Instance Method
class Example
def self.public_m
Example.new.send(:private_m)
end
private
def private_m
puts 'Hello'
end
end
e = Example.public_m
Does not exactly answer the Question, but you can call private methods this way
class Example
private
def example_test
puts 'Hello'
end
end
e = Example.new
e.send(:example_test)
Just in case someone stumbles this now. From Ruby 2.7, calling a private method with a literal self as the receiver is now allowed.
We can verify this as well by running the original ruby snippet against versions 2.6.9 and 3.1.
(ins)tmp->cat sample.rb
class Foo
def public_m
self.private_m # <=
end
private
def private_m
puts 'Hello'
end
end
Foo.new.public_m
# See no exception is raise with 3.1 version
(ins)tmp->ruby -v
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-darwin20]
(ins)tmp->ruby sample.rb
Hello
Now if we try to run the same script with version 2.6.9, we see that exception is raised.
(ins)tmp->ruby -v
ruby 2.6.9p207 (2021-11-24 revision 67954) [x86_64-darwin20]
(ins)tmp->
(ins)tmp->ruby sample.rb
sample.rb:3:in `public_m': private method `private_m' called for #<Foo:0x00007ff95289f870> (NoMethodError)
Did you mean? private_methods
from sample.rb:11:in `<main>'

Resources