ruby access modifiers, different output with different versions 2.5 - 2.7 - ruby

I am trying to run this code and it gives different output with different versions of ruby 2.5 - 2.7
code:
class ParentClass
def the_public_method
self.method1
end
private
def method1
puts "The private has been called"
end
end
class ChildClass < ParentClass
def test
self.method1
end
end
ParentClass.new.the_public_method
ChildClass.new.test
on ruby 2.5 it gives:
Traceback (most recent call last):
1: from main.rb:19:in `<main>'
main.rb:3:in `the_public_method': private method `method1' called for
#<ParentClass:0x000056367ee0b388> (NoMethodError)
Did you mean? method
methods
exit status 1
on ruby 2.7 it gives:
The private has been called
The private has been called
I think the first output is correct with the older version of ruby..
any feedback?

I think the first output is correct with the older version of ruby.. any feedback?
Both outputs are correct. The specification was changed in Ruby 2.7, so naturally Ruby 2.7 behaves different.
Originally, the rule for private methods was "private methods can only be called without an explicit receiver".
However, this means that you can't use private setters, because foo = :bar is a local variable assignment and self.foo = :bar is not allowed.
So, the rule was changed to "private methods can only be called without an explicit receiver, except for setters, where the literal pseudo-variable self is allowed as the receiver".
But, this still doesn't account for things like self + 2 or self.foo += 2 where either +, foo, or foo= are private, and many, many other corner cases.
For a while, the Ruby developers tried to cope with this by either ignoring some of those corner cases, or adding an ever more complex set of exceptions, but really, the solution is rather simple: change to rule to "private methods can only be called with the literal pseudo-variable self as the explicit or implicit receiver".
And that is the rule that stands since Ruby 2.7.

Ruby 2.7 allows calling a private method with self
Before Ruby 2.7, calling a private write/assignment method with literal self as the receiver was allowed, but calling any other private method with self would throw a NoMethodError error.
Ruby 2.7 aims at standardizing the interaction between self and private methods. The above inconsistency has been fixed in Ruby 2.7.
So the first output is correct before Ruby 2.7, the second output is correct after Ruby 2.7.

In Ruby, a private method is still accessible from inherited classes, but used to require a non-explicit received (i.e. an implicit call, like mehtod1 but not obj.method1 or self.method1)
As stated by #eux, this last requirement has been relaxed in ruby 2.7 so you can now call self.method too.
Another quirk with the visibility in Ruby is that it's linked to the instances and not the class itself. This explain the behaviour of private, and let's you understand the following code:
class Foo
def initialize(name)
#name = name
end
def ==(rhs)
name == rhs.name
end
private
attr_reader :name
end
f = Foo.new("bar")
f == f # NoMethodError
Here the NoMethodError occues because attr_reader :name is private, so you cannot access the method name of another object. To enable this behaviour, use protected

Related

Why do Ruby instance method invocations behave differently when prefixed with 'self'?

The two invocations of an instance method carry different semantics. The first call to bar works as expected.
class T
def foo
bar # <= This works. "bar" is printed.
self.bar # <= EXCEPTION: `foo': private method `bar' called for ...
end
private
def bar
puts 'bar'
end
end
t = T.new
t.foo
I'd like to understand why. What causes self.bar to have a different semantics, throwing an exception? I'm not interested in the exception per se since I can work around with the dubious removal of the private label, but primarily interested in the semantic discussion.
Private methods can not be called with explicit receiver like self. This means that you can call a private method from within a class it is declared in as well as all subclasses of this class.
Here is a good article about that. It explains well why code like yours will raise NoMethodError.
On wikibooks there is also a good explanation of visibility levels in ruby.
For the reference you can bypass this with Object#send but generally it is considered as a bad practice to do so.

Using the Class itself as a Method

Today I came across the Pathname class in Ruby and noticed that you could directly call the class itself as a method (which would basically return a new instance):
Pathname("some/path")
# => #<Pathname:some/path>
I've been trying to replicate the same thing with my CustomClass but haven't been successful. I don't know what these methods are called and I can't find any Ruby code that gives me an idea on how to do this. My Question is how do I use the Class name as method?
Things I've tried so far:
Defining self.self()
Defining self.class()
Using the class << self syntax
Googling - But it just returns comparisons of class methods vs instance methods
This isn't using the class itself. This is calling a method in Kernel with the same name as the class. It's generally discouraged to do it yourself as you pollute almost all objects with new methods and leads to confusion (as you already see).
Here is the documentation for the method. There are a few others like Array, Hash, String, etc.
What you're looking for is a conversion method to coerce the input to the instance of the class.
It is not a method of the class itself, but a method in Kernel module. So in order to be able to use the form of MyClass(value) you should add the method to Kernel module:
module Kernel
def Foo(value)
# you can implement any logic here
value.is_a?(Foo) ? value : Foo.new(value)
end
module_function :Foo
end
class Foo
def initialize(bar)
#bar = bar
end
end
baz = Foo('bar')
#=> #<Foo:0x007fd4e5070370 #bar="bar">
Foo(baz)
#=> #<Foo:0x007fd4e5070370 #bar="bar">
baz == Foo(baz)
#=> true
This is not a class call, but a shortcut. And the trickiest part - it was defined for a Kernel module to be available everywhere in the form as you just specified.
Please proceed to the link of the official docs. There you can see, that requiring a Pathname module, it extend Kernel module to add the method of the same name.
To be honest, I strongly recommend against extending Kernel with your own method. Or at least to use refinements

How do I define an instance method in Ruby? [duplicate]

This question already has answers here:
What does a Java static method look like in Ruby?
(2 answers)
Closed 8 years ago.
I'm trying to define a method called function. It works when I do this:
a = A.new
def a.function
puts 100
end
But I want the method to work for any instance variable, not just a. For example, when I call function on an instance of A other than a, nothing happens. So I used A::f to define the function rather than a.function.
class A
attr_accessor :function
attr_accessor :var
end
def A::function
self.var = 0x123
end
a = A.new
a.function
puts a.var
This compiles fine but when I try to call the function I get an error. Why is it not working and how can I do what I'm attempting?
You're really tangled up here. I suggest you check out _why's poignant guide to ruby and get a handle on what is going on here.
As an attempt to steer you right, though…
a isn't an instance variable. It's a local variable that you're using to reference an instance of A.
def a.foo defines a method on the eigenclass of a.
attr_accessor :function already defined A#function (that is, an instance method on A called function) that essentially looks like this: def function; #function; end
def A::function defines a class method on A that you could access via A.function (not an instance of A as in a.function.
MRI doesn't really compile ruby like you might anticipate. It runs it, dynamically interpreting statements in realtime.
You probably want to stick with defining standard instance methods in the traditional manner, and avoid using “function” as a placeholder name since it is a reserved word and has special meaning in other languages. I'll use “foo” here:
class A
def foo
'Awww foo.'
end
end
That's it, you can now create an instance of A (a = A.new) and call foo on it (a.foo) and you'll get back 'Aww foo.'.
class A
def function
puts 100
end
end
a = A.new
a.function #=> "100"
That's a classic instance method. Is that what you're looking for or am I missing something?
If you're trying to define methods dynamically, you could use Class#define_method. Otherwise, if you are just wanting to define the method for the class, defining it in the scope of class A will suffice.
Anyway, could you be more specific on what are you trying to accomplish and what kind of error you're having, please?

Why use extend/include instead of simply defining method in main object?

RSpec adds a "describe" method do the top-level namespace. However, instead of simply defining the method outside of any classes/modules, they do this:
# code from rspec-core/lib/rspec/core/dsl.rb
module RSpec
module Core
# Adds the `describe` method to the top-level namespace.
module DSL
def describe(*args, &example_group_block)
RSpec::Core::ExampleGroup.describe(*args, &example_group_block).register
end
end
end
end
extend RSpec::Core::DSL
Module.send(:include, RSpec::Core::DSL)
What is the benefit of using this technique as opposed to simply defining describe outside any modules and classes? (From what I can tell, the DSL module isn't used anywhere else in rspec-core.)
I made this change a few months ago so that describe is no longer added to every object in the system. If you defined it at the top level:
def describe(*args)
end
...then every object in the system would have a private describe method. RSpec does not own every object in the system and should not be adding describe willy-nilly to every object. We only want the describe method available in two scopes:
describe MyClass do
end
(at the top-level, off of the main object)
module MyModule
describe MyClass do
end
end
(off of any module, so you nest your describes in a module scope)
Putting it in a module makes it easy to extend onto the main object (to add it to only that object, and not every object) and include it in Module (to add it to all modules).
Actually, if that's all there is in the code, I don't really believe it to be much better — if at all. A common argument is that you can easily check that RSpec is responsible for addinng this method in the global namespace by checking the method owner. Somehow it never felt this was needed, as the location of the method already stores that information.
Defining the method outside of any scope would have be equivalent to defining a private instance method in Object:
class Object
private
def double(arg)
arg * 2
end
end
double(3) # OK
3.double(3) # Error: double is private
self.double(3) # Error: double is private
I think privateness is a useful aspect, because it prevents from making certain method calls that have no meaning, that the code shown in the question lacks.
There's an advantge to defining the method in a module, though, but the RSpec code doesn't seem to make use of it: using module_function, not only do you preserve privateness of the instance method, but you also get a public class method. This means that if you have an instance method of the same name, you will still be able to refer to the one defined by the module, by using the class method version.
A common example of module_function is the Kernel module, which contains most function-like core methods like puts (another one is Math). If you're in a class that redefines puts, you can still use Kernel#puts explicitly if you need:
class LikeAnIO
def puts(string)
#output << string
end
def do_work
puts "foo" # inserts "foo" in #output
Kernel.puts "foo" # inserts "foo" in $stdout
end
end

Scoping of Open classes in Ruby versus MOP in Groovy

What I'm trying to find out is whether there is some sort of equivalence to what I see in Groovy as ExpandoMetaClasses. I've been reading about Open Classes but I can't quite see what level of scoping Ruby allows of the class modifications.
Borrowing an example from the blog above, in Groovy, I could modify Java's String class and add a method to it like so:
String.metaClass.shout = {->
return delegate.toUpperCase()
}
println "Hello MetaProgramming".shout()
// output
// HELLO METAPROGRAMMING
And I think that Ruby would have you redefine the class and possibly alias it (please help clarify my misunderstandings at this point):
class String
def foo
"foo"
end
end
puts "".foo # prints "foo"
In Groovy, there are ways to scope the redefinition of core Java library methods to single instances or to a group of instances using Categories, which feel similar to what I would define as mixins in Ruby.
What are the ways to scope open classes to specific instances or to subsets of modules?
If I were to install a gem that had redefined some core class, would only that module be affected, or would any .rb file I include that gem with be affected with it?
Apologies in advance for making some possible assumptions on both Ruby and Groovy, I'm new to both but have been trying to find equivalence between the two.
Ruby's classes are never "closed". So when you say:
class String
def omg!
self.replace "OMG"
end
end
You are defining the omg! method on the String class. Unlike in Groovy, which requires the usage of a special metaclass concept, Ruby classes are always open, period.
If you wanted to modify a particular set of Strings, you could do this:
module Magic
def presto
puts "OMG A HAT!"
end
end
class Array
include Magic
end
x = "Hello".extend(Magic)
puts x #=> Hello
x.presto #=> OMG A HAT!
[].presto #=> OMG A HAT!
def x.really?
true
end
x.really? #=> true
Effectively, a module is a collection of methods that can be added to a class or specific instances.
So you can either open a class directly or add new methods to a class using a module. You can also open an instance directly or add new methods to an instance using a module. That's because a class is just an instance of Class ;) Pretty nifty!
In addition to what Yehuda said, instances in Ruby also have metaclasses (technically called "singleton classes"), accessed with class <<whatever. For example, to redo Yehuda's Magic example with a singleton class:
x = "Hello"
class <<x
include Magic
def magical?
true
end
end
x.presto #=> OMG A HAT!
x.magical? #=> true
"Something else".magical? #=> NoMethodError
There's no scoping on modifications to classes. As soon as a class is modified, the modified class is accessible to all later requires and following code.

Resources