Ruby Failure while calling an objects own private method? - ruby

The method puts is defined as private in the module Kernel. So nobody can execute an other objects puts - that's clear. But why isn't the following exmaple running although self and slf have the same ID? Aren't they the same Object?
>> slf = self
>> slf.puts
(irb):206:in `<main>': private method `puts' called for main:Object (NoMethodError)
from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
from C:/Ruby30-x64/bin/irb:25:in `load'
from C:/Ruby30-x64/bin/irb:25:in `<main>'
>> p self.object_id
320
>> p slf.object_id
320

Private methods in Ruby can only be invoke via a receiverless message send, i.e. with self as the implicit receiver, or with the literal pseudo-variable self as the explicit receiver.
IOW, the two ways that are allowed are
foo(args) and
self.foo(args).
In your example, you are sending a message where the receiver is the local variable slf, i.e. neither implicit, nor the literal pseudo-variable self, therefore, invoking a private method is not allowed.

Related

Why can't we initialize TrueClass in Ruby

Why can't we initialize TrueClass in Ruby? I get this:
TrueClass.new # => NoMethodError: undefined method `new' for TrueClass:Class
But, superclass of TrueClass is Object.
Similarly we are unable to initialize NilClass and FalseClass
I just wanted to know how that is possible even if that is the child class of Object. If we wanted to write a class similar to this how can we achieve it?
I just wanted to know how that is possible even if that is the child class of Object. If we wanted to write a class similar to this how can we achieve it?
You can undefine inherited methods using the undef keyword. Since new is a class method, you'll have to use undef inside the class's singleton class. That would look like this:
class <<MyClass
undef new
end
MyClass.new # NoMethodError: undefined method `new' for MyClass:Class
I just wanted to know how that is possible even if that is the child class of Object.
It works by undefining allocate and new. Here's the corresponding C code:
rb_undef_alloc_func(rb_cTrueClass);
rb_undef_method(CLASS_OF(rb_cTrueClass), "new");
You can achieve a similar result in Ruby via undef_method:
class FooClass
::FOO = new # <- this will be the only Foo instance
class << self
undef_method :allocate
undef_method :new
end
end
FooClass.new #=> NoMethodError: undefined method `new' for FooClass:Class
FooClass.allocate #=> NoMethodError: undefined method `allocate' for FooClass:Class
FOO #=> #<FooClass:0x007fddc284c478>
"similar", because TrueClass.allocate doesn't actually raise a NoMethodError, but a TypeError:
TrueClass.allocate #=> TypeError: allocator undefined for TrueClass
Unfortunately, rb_undef_alloc_func is not available from within Ruby. We could mimic the behavior by overriding allocate:
class FooClass
class << self
def allocate
raise TypeError, "allocator undefined for #{self}"
end
undef_method :new
end
end
FooClass.allocate #=> TypeError: allocator undefined for FooClass
Not sure, which approach is cleaner.
The above changes prevent you from creating an instance via new, but there are other ways:
FOO #=> #<FooClass:0x007fddc284c478>
FOO.dup #=> #<FooClass:0x007fad721122c8>
FOO.clone #=> #<FooClass:0x007f83bc157ba0>
Marshal.load(Marshal.dump(FOO)) #=> #<FooClass:0x007f83bc13e330>
To account for all these special cases, Ruby' stdlib provides the Singleton module:
require 'singleton'
class Foo
include Singleton
end
It works by making allocate and new private methods: (among other changes)
Foo.new #=> NoMethodError: private method `new' called for Foo:Class
Foo.allocate #=> NoMethodError: private method `new' called for
And it adds instance which returns an instance: (or the instance, there's only one)
Foo.instance #=> #<Foo:0x007fdca11117e8>

Remove Kernel Array method

I would like to know how to remove the method Kernel.Array.rand. When the user tries to call it, it should give an error; any kind of error would do.
I tried as below. I tried Kernel.Array and Kernel::Array instead of Random, but they did not work too.
class << Random; self; end.send :remove_method, :rand
Using my IRB I saw that:
2.0.0-p195 :028 > Kernel.Array.rand
ArgumentError: wrong number of arguments (0 for 1)
from (irb):28:in `Array'
from (irb):28
And was even available in the autocomplete with tab.
Tried the rand because was necessary to avoid any use of random method. So I need to remove also sample and shuffle from the Array.
But look what I get:
class << Array; self; end.send :remove_method, :sample
NameError: method `sample' not defined in Class
from (irb):31:in `remove_method'
from (irb):31
So, I would still know how to remove a method from the Array, in that case should be related to the Kernel.Array.
You can use undef_method (the difference with remove_method is that undef_method will walk up the inheritance chain)
rand # => 0.3417719504956065
Kernel.send :undef_method, :rand # private method, have to use `send`
rand # ~> -:5:in `<main>': undefined local variable or method `rand' for main:Object (NameError)
Update
Ah, you are confused. There is a Kernel::Array method, which is completely different from Array the class. Also, there's no need to obfuscate your code with those eigenclass constructs. You can do simply this:
module Kernel
undef_method :rand
end
class Array
undef_method :sample
end
rand # ~> -:9:in `<main>': undefined local variable or method `rand' for main:Object (NameError)
[1, 2].sample # ~> -:10:in `<main>': undefined method `sample' for [1, 2]:Array (NoMethodError)

Call private method in the same class will raise error on Ruby 1.9

I called a private method in initialize, and a no method error was raised. If I comment out the private method, it works fine. I guess I have the wrong concept for using private methods, right?
in `initialize': private method `start' called for #<RemoteFocusAutomation::Autofocus:0x007fcfed00a3d8> (NoMethodError)
The gist code is here https://gist.github.com/poc7667/7299274
Remove self from self.start(args) in your Autofocus#initialize method definition. You shouldn't call private methods with explicit receiver in ruby. it must be implicit call.
Here is one example:
# I tried to call the private method with explicit receiver,which I supposed no to do,
# as Ruby wouldn't allow me,and will give me back error.
class Foo
def initialize
self.foo
end
private
def foo;1;end
end
Foo.new
# `initialize': private method `foo' called for # (NoMethodError)
Now I am doing what Ruby allow me to do:
class Foo
def initialize
foo
end
private
def foo;p 1;end
end
Foo.new # => 1 # works!!

method_missing visibility in Ruby

method_missing shows up in Object.private_methods, not in Object.public_methods.
However, when I call Object.method_missing :stupidmethod, I get
NoMethodError: undefined method `stupidmethod' for Object:Class
I would expect to get
NoMethodError: private method `method_missing' called for Object:Class
because that's what I get when I try to invoke Object's other private methods, e.g. Object.chop.
As more evidence, if I call Object.method_missing without an argument, I get
ArgumentError: no id given
So it seems like I really am invoking that "private" method_missing function from outside of its object. Can you explain this?
EDIT: Thank you to Eugene in the comments. ruby --version tells me 1.8.7. Also, irb --version is 0.9.5(05/04/13). Good to know that this behaves as I'd expect in the later versions.
It is not the private method of Object that is called but the module method in Kernel. You can check which method is called with set_trace_func as described in the answer to a similar question:
irb(main):001:1> set_trace_func proc { |event, file, line, id, binding, classname| printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname if id.to_s == 'method_missing' }
=> #<Proc:0x0423d278#(irb):1>
irb(main):002:0> Object.method_missing :test
c-call (irb):4 method_missing Kernel
c-return (irb):4 method_missing Kernel
NoMethodError: undefined method `test' for Object:Class
from (irb):4
from :0
As some commenters pointed out in MRIs newer than 1.8.7 this behaviour has changed: method_missing has been removed from Kernel and the private instance method from Object was moved to BasicObject which is the new superclass.

Ruby global scope

When answering another question, I realized that the following program does not quite do what I thought it does.
puts "test"
self.puts "test" # => private method `puts' called for main:Object (NoMethodError)
The exception surprises me, as I always thought that top-level method calls would be resolved by the main object instance, but this doesn't seem to be the case.
Who's the actual receiver of the first call and how is it resolved? Is this a special rule that only applies to method calls at the top-level scope?
Here is a good discussion that talks about this question.
The top level methods, which are provided by Kernel, are automatically included to the Object class. This means the Kernel methods will appear in everything.
The error private method 'puts' called for main:Object (NoMethodError) is just stating that puts exists but is privately scoped.
ree-1.8.7-2011.03 :001 > puts "test"
test
ree-1.8.7-2011.03 :004 > self.send(:puts, "hi" )
hi
UPDATE
There is no magic for Kernel methods. There is no scope hopping or anything. I think the confusion lines in what the scope is when using self. You do not have access to private methods using self.
class PutsTest
def success_puts
private_puts
end
def failed_puts
# trying to access a private method from self
self.private_puts
end
private
def private_puts
puts 'hi'
end
end
By using self, you are changing the scope from calling the method inside of PutsTest to calling from the outside of PutsTest
ree-1.8.7-2011.03 :095 > test = PutsTest.new
ree-1.8.7-2011.03 :096 > test.success_puts
hi
ree-1.8.7-2011.03 :097 > test.failed_puts
NoMethodError: private method `private_puts' called for #<PutsTest:0xd62c48>

Resources