Understanding private methods in Ruby - 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>'

Related

Private methods calling in Ruby

What my knowledge says about Ruby is that private methods cannot be called with explicit receiver even self.
class Private
private
def private_method(c)
puts '#{c}'
end
end
p = Private.new
p.instance_eval{ private_method("private method called") }
How does this work? We can call private method with instance_eval. Please explain
Ruby allows you to do a lot of "nasty" things that break encapsulation or whichever other principle.
instance_eval runs the block with self being p. And you can obviously call private methods when you're in the instance. You can even define methods that way!
class A
end
a = A.new
a.instance_eval{def value; 5; end}
puts a.value # prints "5"
It's not the only way you can do this. send is also allowed to call private methods:
class A
private
def p
5
end
end
a = A.new
puts a.send(:p) # prints "5"
Because instance_eval executes the block in the context of p (meaning, it is self) and the call to private_method now is a call with implicit receiver.

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

Using define_method in a loop to create methods in Ruby (Through Selenium Webdriver)

I'm trying to create a method to dynamically do the following: (as I will have to implement this on about 30 different sets of sub-classes)
def t1
FooT1.new
end
def t2
FooT2.new
end
def t3
FooT3.new
end
Where there will be 2 variables in the method generation, the tab number(t1...tx) and the name of the class (Foo)
I tried the following, but I'm new to Ruby and can not get this working.
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
define_method("t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
Then call it in the sub-class like so:
method_generator(3, "Bar")
I'm aware I'm probably quite far off in implementing this, so any help is appreciated.
Just do as below :
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
class_name.send(:define_method,"t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
Module#define_method is a private method, thus you can't call it on the class_name like class_name.define_method(:name) do ..end, as private method call not allows explicit receiver. But to do so Object#send will help you, as this method is here for this kind of scenarios, where you can't call private method by explicit receiver.
Lets verify with an example, if this tricks works or not.
class Foo;end
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
class_name.send(:define_method,"t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
method_generator(3,Foo)
Foo.instance_methods(false)
# => [:t1, :t2, :t3] # see here 3 instance methods has been created of class Foo

private and protected methods in Ruby

The following code works:
class MyClass
def method_a
method_b
end
private
def method_b
puts "Hello!"
end
end
m = MyClass.new
m.method_a
Changing the call to method_b to self.method_b however does not work:
def method_a
self.method_b
end
I get a NoMethodError. I'm under the impression that self just resolves to the instance of the class when inside an instance method. Why does self.method_b cause problems?
Note: self.method_b works when private is changed to protected.
Note: if the above methods are changed to class methods then calling self.method_b from method_a doesn't throw the NoMethodError.
This is how private methods work in Ruby. They cannot be called with an explicit receiver (unless it is a setter method; see below).
Read more in the section on Access Control from the Pickaxe.
Private methods whose names end with an = may be invoked using self.method_name = ... as this is necessary to differentiate them from setting a local variable:
class Foo
def try
set_foo(1) # Invokes private method
self.set_foo(2) # ERROR: private method `set_foo' called for ...
self.send(:set_foo,3) # Invokes private method
bar = 1 # Sets local variable
self.bar = 2 # Invokes private method
self.send(:bar=,3) # Invokes private method
end
private
def set_foo(v)
#foo = v
end
def bar=(v)
#bar = v
end
end
Foo.new.bar = 42 # ERROR: private method `bar=' called for ...
That's just how Ruby works: when you provide an explicit object reference, NoMethodError is raised to show the code is breaking intent. You could do a self.send and it would work.
Without the explicit reference, Ruby doesn't do the same visibility check; see this and/or this for a bit more detail.
Nutshell is that private methods can't be called with an explicit receiver, even if it's self.

Explicitly specify default method target in Ruby?

Is there a best practice in Ruby when it comes to explicitly specifying method targets even when unnecessary?
class Foo
def meth1
puts "bar"
end
def meth2
# is this better?
self.meth1
# or this?
meth1
end
end
No, it's just a question of style.
The only thing to keep in mind is that you always have to specify a target for setter methods.
foo_count = 4 #creates a local variable named foo_count and sets it to 4
self.foo_count = 4 #sends foo_count=(4) to self
The same rule applies if you happen to have a local variable with the same name as a method of your class, though I would say that is a bad practice in itself.
As Chuck said earlier, it is mostly a matter of style with the exception he pointed out, and when using private methods. Any time you use a private method from within an object, you must leave off the self. business.
Eg:
class Tze
def meth2
meth1
end
def meth3
self.meth1
end
private
def meth1
puts "method 1 invoked"
end
end
Calling Tze.new.meth2 produces the expected output; however, calling Tze.new.meth3 raises an error because of meth1 is being invoked as self.meth1.

Resources