Behaviour of instance_eval - ruby

My understanding of instance_eval was that if I have module M then the following were equivalent:
module M
def foo
:foo
end
end
class C
class << self
include M
end
end
puts C.foo
equivalent to:
module M
def foo
:foo
end
end
class C
end
C.instance_eval do
include M
end
puts C.foo
However, the first example prints :foo and the second throws a NoMethodError? (Ruby 2.3.0)
In both cases above, if I had replaced:
include M
with:
def foo
:foo
end
ie directly defining the method rather than including a module then both cases would have resulted in a C.foo method being defined. Should I be surprised at this difference between include and defining the method directly?
Or does it ever even make sense to call include within the context of instance_eval? Should it only ever be called within a class_eval?

In each of these cases, what object are you calling include on? In your first example, you're calling include on C's singleton class:
class C
class << self
p self == C.singleton_class
include M
end
end
# => true
p C.foo
# => :foo
...so your include line is equivalent to C.singleton_class.include(M).
In your second example, however, you're calling include on C itself:
class C
end
C.instance_eval do
p self == C
include M
end
# => true
p C.foo
# => NoMethodError: undefined method `foo' for C:Class
p C.new.foo
# => :foo
...so you're doing the equivalent of C.include(M), which is the same as:
class C
p self == C
include M
end
# => true
p C.new.foo
# => :foo
What would work like you want would be to call instance_eval on C's singleton class:
class D
end
D.singleton_class.instance_eval do
p self == D.singleton_class
include M
end
# => true
p D.foo
# => :foo

Module#class_eval() is very different from Object#instance_eval(). The instance_eval() only changes self, while class_eval() changes both self and the current class.
Unlike in your example, you can alter class_instance vars using instance_eval though, because they are in the object scope as MyClass is a singleton instance of class Class.
class MyClass
#class_instance_var = 100
##class_var = 100
def self.disp
#class_instance_var
end
def self.class_var
##class_var
end
def some_inst_method
12
end
end
MyClass.instance_eval do
#class_instance_var = 500
def self.cls_method
##class_var = 200
'Class method added'
end
def inst_method
:inst
end
end
MyClass.disp
#=> 500
MyClass.cls_method
#=> 'Class method added'
MyClass.class_var
#=> 100
MyClass.new.inst_method
# undefined method `inst_method' for #<MyClass:0x0055d8e4baf320>
In simple language.
If you have a look in the upper class defn code as an interpreter, you notice that there are two scopes class scope and object scope. class vars and instance methods are accessible from object scope and does not fall under jurisdiction of instance_eval() so it skips such codes.
Why? because, as the name suggests, its supposed to alter the Class's instance(MyClass)'s properties not other object's properties like MyClass's any object's properties. Also, class variables don’t really belong to classes—they belong to class hierarchies.
If you want to open an object that is not a class, then you can
safely use instance_eval(). But, if you want to open a class definition and define methods with def or include some module, then class_eval() should be your pick.
By changing the current class, class_eval() effectively reopens the class, just like the class keyword does. And, this is what you are trying to achieve in this question.
MyClass.class_eval do
def inst_method
:inst
end
end
MyClass.new.inst_method
#=> :inst

Related

How to call one class method from another class method

I am relatively new to Ruby and am having a hard time understanding class methods. If I have a class like the one below,
class Foo
def self.a
end
def self.b
end
end
How do I call a from b?
Just use its name:
class Foo
def self.a
puts 'a'
end
def self.b
puts 'b'
a
end
end
[23] pry(main)> Foo.a
a
=> nil
[24] pry(main)> Foo.b
b
a
=> nil
Firstly, recall that classes are modules with certain properties. Let's first look at modules. One built-in module is the Math module. You will see from the doc that Math has no instance methods (so there would be no point to include or prepend that module into a class); all of its methods are module methods. That means they are invoked with an explicit receiver: Math.sqrt(10). Math is merely a library of helper methods, available for use by all modules (and hence, classes).
Let's add a module method to Math:
module Math
def Math.sqr(x)
x*x
end
end
Math.sqr(3)
#=> 9
Suppose we decided to change the name of this module to Maths. We would then have to rewrite this method:
module Maths
def Maths.sqr(x)
x*x
end
end
If is for that reason module methods are generally defined def self.method(...), as self equals the module in which the method is defined. That way, if the module name is changed the module methods don't have to be redefined.
Since classes are modules, class methods are just another name for module methods (though class methods are inherited by subclasses), and the foregoing applies to classes and class methods as well.
Like all methods, module methods are invoked by prefacing the method with its receiver. For example, Math.sqrt(10). Similarly, like all methods, the method can be invoked without including its receiver (i.e., the receiver is implicit) if self equals its receiver at the time it is invoked. A module method's receiver equals self only when the method is invoked from within the module in which it was defined, outside a method, or from with another module method defined in the same module. For example, since sqrt is a module method in Math, I could write:
module Math
def Math.sqrt_times_2(x)
puts "self = #{self}"
2 * sqrt(x)
end
end
Math.sqrt_times_2(25)
# self = Math
#=> 10.0
Since classes are modules all of this applies to class methods as well, but this extends to subclasses as well, through inheritance. To invoke a class method klass_method defined in class Klass, we could invoke the method Klass.klass(...) (possibly followed by a block) from any class, but could (and should) drop the explicit receiver Klass. if it were invoked from within Klass or within a subclass of Klass. Here's an example.
class Klass
def self.klass_method(n)
puts "In klass_method, self = #{self}"
2 * n
end
def self.other_klass_method(n)
puts "In other_klass_method, self = #{self}"
3 * klass_method(n)
end
end
class SubKlass < Klass
def self.sub_klass_method(n)
puts "In sub_klass_method, self = #{self}"
5 * klass_method(n)
end
def sub_klass_instance_method(n)
puts "In sub_klass_instance_method, self = #{self}"
5 * self.class.klass_method(n)
end
end
Klass.klass_method(2)
# In klass_method, self = Klass
#=> 4
Klass.other_klass_method(3)
# In other_klass_method, self = Klass
# In klass_method, self = Klass
#=> 18
SubKlass.sub_klass_method(4)
# In sub_klass_method, self = SubKlass
# In klass_method, self = SubKlass
#=> 40
si = SubKlass.new
# In sub_klass_instance_method, self = #<SubKlass:0x0000586729649c70>
# In klass_method, self = SubKlass
si.sub_klass_instance_method(3)
# In klass_method, self = SubKlass
#=> 30
The last statement above shows that, to invoke a class method from within an instance method, the method's receiver must be set equal to the class in which the instance method is defined.

Class-like closures in ruby

I'm trying to do something like this in Ruby:
class A
def b(c)
Class.new do
def d
c
end
end
end
end
class B < A.b('whoa'); end
#
# What I want:
#
B.new.d # => 'whoa'
#
# What I get:
#
# NameError: undefined local variable or method `d' for B:0xfdfyadayada
Is there any way to do this?
The context is I'm trying to get some code reuse by constructing classes that are mostly similar except for some minor config.
I'm looking for an API similar to Sequel::Model where you can do something like:
class Order < Sequel::Model(:order)
The contents of a method definition using the def keyword are not lexically scoped the way blocks are. In other words you can’t do something like this:
foo = 7
def bar
# Code here inside the method definition can't
# "see" foo, so this doesn't work.
foo
end
However you can use the define_method method to dynamically define a method using a block, and the block will be able to refer to local variables in its outer scope. So in your case you could do this (I’ve also changed def b to def self.b, which I think is what you meant):
class A
def self.b(c)
Class.new do
define_method(:d) do # N.B. the d here is a symbol
c
end
end
end
end
class B < A.b('whoa'); end
B.new.d # => 'whoa'
This would also work if you created other classes:
class C < A.b('dude'); end
C.new.d # => 'dude'
# B still works
B.new.d # => 'whoa'
Assuming that b is a class method (i.e def self.b instead of def b), you could store c in a class variable.
class A
def self.b(c)
Class.new do
##c = c
def d
##c
end
end
end
end
class B < A.b('whoa'); end
puts B.new.d # => whoa
There could be a better solution, I haven't looked at the source code of Sequel. This is just the first thing that came to my mind.

Evaluating a constant within a module

When class keyword is used, constant lookup is done within that class. In the following, what is assigned to :bar is B::A, not ::A.
A = :foo
class B
A = :bar
end
A # => :foo
But in method definition, I cannot use the keyword class, and if I use things like class_eval, module_eval, instance_eval, to evaluate a block, then the constant referred to would be evaluated in the main environment as follows.
class B; end
def foo &pr
B.class_eval(&pr)
end
foo{A = :bar}
A # => :bar
Is there a way to pass a block to a method and have its constant be evaluated within a certain class/module?
I think I see what you're asking (though I don't yet understand why you need to). You could yield the class back to the block so at least you're explicit about what is happening:
def foo &pr
yield self.class
end
my_object.foo {|klass| klass::A = :bar }
Module#const_set seems to be what you want:
class B; end
def foo(klass, konstant, val)
klass.const_set(konstant, val)
end
foo(B, "A", :bar)
B::A #=> :bar
A #=> NameError: uninitialized constant A
...but since you didn't mention it, I expect I've misunderstood the question.

Why are constants from extended module not available in class methods declared with self.?

I thought there were no differences between methods declared within a class << self block and those declared with a self. prefix, but there are:
module A
VAR = 'some_constant'
end
class B
extend A
class << self
def m1
puts VAR
end
end
def self.m2
puts VAR
end
end
B.m1 # => OK
B.m2 # => uninitialized constant B::VAR
Why are constants of A available in m1 but not in m2?
In Ruby, constant lookup is not the same as method lookup. For method lookup, calling foo is always the same as calling self.foo (assuming it isn't private). Calling a constant FOO is very different from self::FOO or singleton_class::FOO.
Using an unqualified constant (e.g. FOO) will do a lookup in the currently opened modules. A module is opened with module Mod, class Klass, class << obj, or module_eval and variants. When defining m1, these are B, and then B.singleton_class. When defining m2, only B is opened.
module Foo
X = 42
class Bar
def self.hello
X
end
end
end
In this code, Foo::Bar.hello will return 42, even though X is not a constant of Bar, its singleton class or ancestor. Also, if you later add a constant X to Bar, then that value will be returned. Finally, the following definition is not equivalent:
module Foo
X = 42
end
class Foo::Bar
def self.hello
X
end
end
Foo::Bar.hello # => uninitialized constant Foo::Bar::X
Indeed, when hello is defined, only the class Foo::Bar is opened, while in the previous example, both Foo and Foo::Bar where opened.
A last example, to show the difference an explicit scope can have with inheritance:
class Base
X = 42
def self.foo
X
end
def self.bar
self::X
end
end
class Parent < Base
X = :other
end
Parent.foo # => 42
Parent.bar # => :other
In your case, you probably want to include your module, instead of extending it, no?
Otherwise, you could use singleton_class::VAR, your code will work as you expect it.
module A
VAR = 'some_constant'
end
class B
extend A
class << self
def m1
puts singleton_class::VAR # not necessary here, as singleton_class is opened
end
end
def self.m2
puts singleton_class::VAR # necessary here!
end
end
B.m1 # => OK
B.m2 # => OK

Inheriting class methods from modules / mixins in Ruby

It is known that in Ruby, class methods get inherited:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
However, it comes as a surprise to me that it does not work with mixins:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
I know that #extend method can do this:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
But I am writing a mixin (or, rather, would like to write) containing both instance methods and class methods:
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Now what I would like to do is this:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
I want A, B inherit both instance and class methods from Common module. But, of course, that does not work. So, isn't there a secret way of making this inheritance work from a single module?
It seems inelegant to me to split this into two different modules, one to include, the other to extend. Another possible solution would be to use a class Common instead of a module. But this is just a workaround. (What if there are two sets of common functionalities Common1 and Common2 and we really need to have mixins?) Is there any deep reason why class method inheritance does not work from mixins?
A common idiom is to use included hook and inject class methods from there.
module Foo
def self.included base
base.send :include, InstanceMethods
base.extend ClassMethods
end
module InstanceMethods
def bar1
'bar1'
end
end
module ClassMethods
def bar2
'bar2'
end
end
end
class Test
include Foo
end
Test.new.bar1 # => "bar1"
Test.bar2 # => "bar2"
Here is the full story, explaining the necessary metaprogramming concepts needed to understand why module inclusion works the way it does in Ruby.
What happens when a module is included?
Including a module into a class adds the module to the ancestors of the class. You can look at the ancestors of any class or module by calling its ancestors method:
module M
def foo; "foo"; end
end
class C
include M
def bar; "bar"; end
end
C.ancestors
#=> [C, M, Object, Kernel, BasicObject]
# ^ look, it's right here!
When you call a method on an instance of C, Ruby will look at every item of this ancestor list in order to find an instance method with the provided name. Since we included M into C, M is now an ancestor of C, so when we call foo on an instance of C, Ruby will find that method in M:
C.new.foo
#=> "foo"
Note that the inclusion does not copy any instance or class methods to the class – it merely adds a "note" to the class that it should also look for instance methods in the included module.
What about the "class" methods in our module?
Because inclusion only changes the way instance methods are dispatched, including a module into a class only makes its instance methods available on that class. The "class" methods and other declarations in the module are not automatically copied to the class:
module M
def instance_method
"foo"
end
def self.class_method
"bar"
end
end
class C
include M
end
M.class_method
#=> "bar"
C.new.instance_method
#=> "foo"
C.class_method
#=> NoMethodError: undefined method `class_method' for C:Class
How does Ruby implement class methods?
In Ruby, classes and modules are plain objects – they are instances of the class Class and Module. This means that you can dynamically create new classes, assign them to variables, etc.:
klass = Class.new do
def foo
"foo"
end
end
#=> #<Class:0x2b613d0>
klass.new.foo
#=> "foo"
Also in Ruby, you have the possibility of defining so-called singleton methods on objects. These methods get added as new instance methods to the special, hidden singleton class of the object:
obj = Object.new
# define singleton method
def obj.foo
"foo"
end
# here is our singleton method, on the singleton class of `obj`:
obj.singleton_class.instance_methods(false)
#=> [:foo]
But aren't classes and modules just plain objects as well? In fact they are! Does that mean that they can have singleton methods too? Yes, it does! And this is how class methods are born:
class Abc
end
# define singleton method
def Abc.foo
"foo"
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
Or, the more common way of defining a class method is to use self within the class definition block, which refers to the class object being created:
class Abc
def self.foo
"foo"
end
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
How do I include the class methods in a module?
As we just established, class methods are really just instance methods on the singleton class of the class object. Does this mean that we can just include a module into the singleton class to add a bunch of class methods? Yes, it does!
module M
def new_instance_method; "hi"; end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
self.singleton_class.include M::ClassMethods
end
HostKlass.new_class_method
#=> "hello"
This self.singleton_class.include M::ClassMethods line does not look very nice, so Ruby added Object#extend, which does the same – i.e. includes a module into the singleton class of the object:
class HostKlass
include M
extend M::ClassMethods
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ there it is!
Moving the extend call into the module
This previous example is not well-structured code, for two reasons:
We now have to call both include and extend in the HostClass definition to get our module included properly. This can get very cumbersome if you have to include lots of similar modules.
HostClass directly references M::ClassMethods, which is an implementation detail of the module M that HostClass should not need to know or care about.
So how about this: when we call include on the first line, we somehow notify the module that it has been included, and also give it our class object, so that it can call extend itself. This way, it's the module's job to add the class methods if it wants to.
This is exactly what the special self.included method is for. Ruby automatically calls this method whenever the module is included into another class (or module), and passes in the host class object as the first argument:
module M
def new_instance_method; "hi"; end
def self.included(base) # `base` is `HostClass` in our case
base.extend ClassMethods
end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
def self.existing_class_method; "cool"; end
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ still there!
Of course, adding class methods is not the only thing we can do in self.included. We have the class object, so we can call any other (class) method on it:
def self.included(base) # `base` is `HostClass` in our case
base.existing_class_method
#=> "cool"
end
As Sergio mentioned in comments, for guys who are already in Rails (or don’t mind depending on Active Support), Concern is helpful here:
require 'active_support/concern'
module Common
extend ActiveSupport::Concern
def instance_method
puts "instance method here"
end
class_methods do
def class_method
puts "class method here"
end
end
end
class A
include Common
end
You can have your cake and eat it too by doing this:
module M
def self.included(base)
base.class_eval do # do anything you would do at class level
def self.doit #class method
##fred = "Flintstone"
"class method doit called"
end # class method define
def doit(str) #instance method
##common_var = "all instances"
#instance_var = str
"instance method doit called"
end
def get_them
[##common_var,#instance_var,##fred]
end
end # class_eval
end # included
end # module
class F; end
F.include M
F.doit # >> "class method doit called"
a = F.new
b = F.new
a.doit("Yo") # "instance method doit called"
b.doit("Ho") # "instance method doit called"
a.get_them # >> ["all instances", "Yo", "Flintstone"]
b.get_them # >> ["all instances", "Ho", "Flintstone"]
If you intend to add instance, and class variables, you will end up pulling out your hair as you will run into a bunch of broken code unless you do it this way.

Resources