In Metaprogramming Ruby, it told us that not only self but also a current class exists in the Ruby.
My question is: how to know what the current class is now? if we define a method, where the definition is put? What confuses me is the difference between these two codes. The running result is not the same.
It indicates that maybe when we want to define a method, we use class to open a class, and then define self.method, is not the same as using class << self to open a class, and then define methods. Actually, it would not use in practical work, but I just want to know.
class C
def self.m1
puts "when in m1, self is #{self}"
def m2; end
end
end
class D < C
end
C.m1
# when in m1, self is C
C.instance_methods false
#=> [:m2]
C.methods false
#=> [:m1]
class C
class << self
def m1
puts "when in m1, self is #{self}"
def m2
end
end
end
end
class D < C; end
C.m1
# when in m1, self is C
C.instance_methods false
#=> []
C.methods false
#=> [:m1, :m2]
The current class of first code snippet is C. Although you define m2 in self.m1, the current class is C. m2 is the instance method.
The current class of the second snippet is singleton_class of C. when you write class << self, you open a singleton class of the current class, C in this case. So the m2 is an instance method of singleton_class of C.
Related
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.
Consider the following Ruby example of a class that includes a module which defines a class and instance method, and a second class that inherits the first.
module Z
def self.included(base)
class << base
def classmethod
puts "Hello, I'm #{__method__} in #{self}"
end
end
end
def instancemethod
puts "Hello, I'm #{__method__} in #{self}"
end
end
class A
include Z
end
class B < A
end
A.classmethod
A.new.instancemethod
B.classmethod
B.new.instancemethod
The output is as expected: both classes and instances have the methods defined by the module.
Hello, I'm classmethod in A
Hello, I'm instancemethod in #<A:0x85f4c10>
Hello, I'm classmethod in B
Hello, I'm instancemethod in #<B:0x85f4968>
Now, consider the scenario where the above module is included in another module which classes include instead:
module Y
include Z
end
class C
include Y
end
class D < C
end
Y.classmethod
C.classmethod
C.new.instancemethod
D.classmethod
D.new.instancemethod
The instance methods work but the class methods on C and D don't (the classmethod is instead defined, where it isn't wanted, on the intermedate module Y).
Can the module Z be modifed so that class methods are added to the class C rather than the intermediate module, Y ?
The original scenario (i.e. where the module isn't nested) should also continue to work as above.
Try this.
module Y
def self.included(base)
Z.included(base)
end
end
class C
include Y
end
class D < C
end
Y.classmethod
#=> NoMethodError: undefined method `classmethod' for Y:Module
C.classmethod
#=> Hello, I'm classmethod in C
C.new.instancemethod
#=>Hello, I'm instancemethod in #<C:0x007ff58403d370>
D.classmethod
#=> Hello, I'm classmethod in D
D.new.instancemethod
#=> Hello, I'm instancemethod in #<D:0x007ff5842478f0>
Four out of five is not bad, eh? I've run out of time now, but will see if I can get Y.classmethod to work. Perhaps a reader may have a suggestion.
#cary-swoveland's idea led me to try this, which solves the original problem: it is a modification to Z that works when it is nested:
module Z
def self.included(base)
class << base
def classmethod
puts "Hello, I'm #{__method__} in #{self}"
end
def included(base)
Z.included(base)
end
end
end
def instancemethod
puts "Hello, I'm #{__method__} in #{self}"
end
end
I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end
Suppose I have a class A as follows:
class A
class B
end
class C < B
end
...
end
And I want to create a class D that has the same nested class structure as A
class D
Replicate nested class structure of A here
end
I want D to look like:
class D
class B
end
class C < B
end
...
end
So that I can do A::B and D::B with different results
How can I achieve this? Thanks in advance.
class Module
def replicate m
m.constants.each do |sym|
case mm = m.const_get(sym)
when Class then const_set(sym, Class.new(mm.superclass)).replicate(mm)
when Module then const_set(sym, Module.new(mm.superclass)).replicate(mm)
end
end
end
end
class D
replicate A
end
But the superclass part may not be correct with this code.
class A
end
class D
end
[A, D].each do |c|
c.class_eval %Q(
class B
def bar; puts "B#bar in #{c} is not shared" end # <--- new
end
class C < B
def foo; puts "C#foo in #{c}" end
end
)
end
p A.constants
p A::C.instance_methods(false)
p D.constants
p D::C.instance_methods(false)
A::C.new.foo
D::C.new.foo
New
A::B.new.bar
D::B.new.bar
=begin
class B # creates a new class B
def quux; puts "A::B#quux in #{self}" end
end
A::B.new.quux #=> undefined method `quux' for #<A::B:0x101358a98> (NoMethodError)
=end
class A::B # reopens B in A
def quux; puts "A::B#quux in #{self}" end
end
A::B.new.quux
Execution :
$ ruby -w t.rb
["B", "C"]
["foo"]
["B", "C"]
["foo"]
C#foo in A
C#foo in D
New
B#bar in A is not shared
B#bar in D is not shared
A::B#quux in #<A::B:0x10402da28>
It's more duplicating than replicating the whole internal structure, including the methods and possible variables. For this you need reflection, or maybe marshall out and in.
New : If you put something in the text inside %Q(), class_eval will evaluate it for each class, hence it is not shared. B is not independent, you have two different classes A::B and D::B.
If you want to add the same code to both classes, create a module and include it. Ruby creates a proxy which points to the module and inserts the proxy in the chain of pointers starting from the class of the object to its superclass, so that the search method mechanism will look for module's methods after methods of the class and before methods in the superclass.
class D
extend A
end
will define instance methods of A as class (singleton) methods of D. Sounds ugly. I think that you should experiment, display what happens with puts, p, instance_methods, singleton_methods and the like.
I want the following module to be included in a class I have:
module InheritanceEnumerator
def self.included(klass)
klass.instance_eval do
instance_variable_set('#subclasses',[])
def self.subclasses
#subclasses
end
original_method = self.respond_to?(:inherited) ? self.public_method(:inherited) : nil
instance_variable_set('#original_inherited_method', original_method)
def self.inherited(subclass)
#original_inherited_method.call(subclass) if #original_inherited_method
#subclasses<<subclass
end
end
end
end
What I'm trying to achieve is that I want my parent class to have references to direct children. I also need any other previous "inherited" methods set on my class by other stuff to stay in place. What am I doing wrong?
Your code works in this situation (for me):
class C; include InheritanceEnumerator; end
C.subclasses #=> []
class C1 < C; end
class C2 < C; end
C.subclasses #=> [C1, C2]
But fails in the following situation:
class C11 < C1; end
C1.subclasses => NoMethodError: undefined method `<<' for nil:NilClass
This is because you are only initializing #subclasses when the module is included; but you are forgetting that subclasses of C also have access to the modules methods but do not explictly include it.
You fix this by doing the following:
def self.subclasses
#subclasses ||= []
#subclasses
end
def self.inherited(subclass)
#original_inherited_method.call(subclass) if #original_inherited_method
#subclasses ||= []
#subclasses << subclass
end
EDIT:
Okay, in future, please state what your problem is more fully and provide the test code you are using; as this was an exercise in frustration.
The following works fine with your code:
class C
def self.inherited(s)
puts "inherited by #{s}!"
end
include InheritanceEnumerator
end
class D < C; end #=> "inherited by D!"
C.subclasses #=> [D]
Perhaps the reason it wasn't working for you is that you included InheritanceEnumerator before you had defined the inherited method?