I was under the impression that refinements fell outside the usual inheritance scheme in Ruby; that overriding a method within a refinement replaced the original method for all code using the refinement.
But then, I tried this experiment with super, and it appears that the overridden method gets called:
class MyClass
def my_instance_method
puts "MyClass#my_instance_method"
end
end
module MyRefinement
refine(MyClass) do
def my_instance_method
puts "MyClass#my_instance_method in MyRefinement"
super
end
end
end
using MyRefinement
MyClass.new.my_instance_method
The above code outputs:
MyClass#my_instance_method in MyRefinement
MyClass#my_instance_method
My question is, how? Is the refinement inserted into the class hierarchy in some way?
Based on the documentation, the method lookup for a refinement's built in behaviour is the same as you have observed.
Your assumption was correct that it is not typical inheritance, that can be seen by invoking superclass
class C
def foo
puts "C#foo"
end
end
module M
refine C do
def foo
puts "C#foo in M"
puts "class: #{self.class}"
puts "superclass: #{self.class.superclass}"
super
end
end
end
using M
x = C.new
x.foo
The output:
C#foo in M
class: C
superclass: Object
C#foo
Related
My question is based on an answer to the topic “redefining a single ruby method on a single instance with a lambda”.
How can I redefine a method and from within the new method call the original definition? Other instances of some_object's class should not become affected.
def some_object.some_method
# call original `some_object.some_method` here
do_something_else
end
If some_object.some_method is not a singleton method, then you can just call super in your redefined method.
def some_object.some_method
super
do_something_else
end
If some_object.some_method is a singleton method, then
Ruby >= 2.0.0
You can define that method in a module
module SomeModule
def some_method
super
do_something_else
end
end
And then prepend it to the singleton class of the object
some_object.singleton_class.prepend(SomeModule)
Ruby < 2.0.0
You have to make an alias then redefine, since there is no Module#prepend.
class << some_object # open the singleton class of some_object
alias some_method_original some_method
def some_method
some_method_original
do_something_else
end
end
class Klass
def greeting
puts "hiya"
end
def add_personal_greeting(str)
define_singleton_method(:greeting) do
super
puts str
end
end
end
Bob gets a handle and tries the standard greeting.
bob = Klass.new
#=> #<Klass:0x007fa66b084ad0>
bob.greeting
# hiya
He finds that too impersonal so he decides to add "I'm Bob" after the greeting. He does that by defining a method greeting on his singleton class that calls Klass's instance method greeting and then adds another line to the greeting.
bob.add_personal_greeting("I'm Bob")
He tries it.
bob.greeting
# hiya
# I'm Bob
Much better. Note
bob.singleton_class.superclass
#=> Klass
Meanwhile, Lucy tries the standard greeting.
lucy = Klass.new
#=> #<Klass:0x007fa66a9ed050>
lucy.greeting
# hiya
She's not wild about it, but it will do.
Bob decides to change his greeting.
bob.add_personal_greeting("I'm Robert")
and tries it.
bob.greeting
# hiya
# I'm Robert
I found this neat delegator based 'tee' implementation on SO:
https://stackoverflow.com/a/6410202/2379703
And I'm curious what is means for #targets (instance variable) means in the context of a class method:
require 'logger'
class MultiDelegator
def initialize(*targets)
#targets = targets
end
def self.delegate(*methods)
methods.each do |m|
define_method(m) do |*args|
#targets.map { |t| t.send(m, *args) }
end
end
self
end
class <<self
alias to new
end
end
log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
I get that it defining the methods write/close but #targets isn't even defined at this point since .to (aliased to new) has yet to be called so I'd assume #targets is nil.
Can anyone give an explanation as to the logistics of how this code works? Does ruby not even attempt to access/resolve #targets until the method in question is attempted to be called, which would be by the logger after it was instantiated?
The define_method method is called on a class to create an instance method. Inside that method, the self (and the instance variable) are instances of the class.
For example:
class Foo
#bar = "CLASS"
def initialize
#bar = "INSTANCE"
end
def self.make_method
define_method :whee do
p #bar
end
end
end
begin
Foo.new.whee
rescue NoMethodError=>e
puts e
end
#=> undefined method `whee' for #<Foo:0x007fc0719794b8 #bar="INSTANCE">
Foo.make_method
Foo.new.whee
#=> "INSTANCE"
It is correct that you can ask about instance variables that have never been created, at any time:
class Bar
def who_dat
puts "#dat is #{#dat.inspect}"
end
end
Bar.new.who_dat
#=> dat is nil
The same is true of other aspects of the language. As long as the code in the method is syntactically valid, it may be defined, even if invoking it causes a runtime error:
class Jim
def say_stuff
stuff!
end
end
puts "Good so far!"
#=> Good so far!
j = Jim.new
begin
j.say_stuff
rescue Exception=>e
puts e
end
#=> undefined method `stuff!' for #<Jim:0x007f9c498852d8>
# Let's add the method now, by re-opening the class
class Jim # this is not a new class
def stuff!
puts "Hello, World!"
end
end
j.say_stuff
#=> "Hello, World!"
In the above I define a say_stuff method that is syntactically valid, but that calls a method that does not exist. This is find. The method is created, but not invoked.
Then I try to invoke the method, and it causes an error (which we catch and handle cleanly).
Then I add the stuff! method to the class. Now I can run the say_stuff method (on the same instance as before!) and it works just fine.
This last example shows how defining a method does not run it, or require that it would even work when it is run. It is dynamically evaluated each time it is invoked (and only at that time).
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.
class Foo
include Module.new { class_eval "def lab; puts 'm' end" }
def lab
super
puts 'c'
end
end
Foo.new.lab #=> m c
========================================================================
class Foo
include Module.new { instance_eval "def lab; puts 'm' end" }
def lab
super
puts 'c'
end
end
Notice here I changed class_eval to instance_eval
Foo.new.lab rescue nil#=> no super class method lab
Foo.lab #=> undefined method lab for Foo class
So it seems that including the module neither defined an instance method nor a class method.
Any explanation what's going on here?
This code was tested on ruby 1.8.7 on mac.
First, think of what include does. it makes the instance methods of the module being included into instance methods on the including class. i.e. apart from the fact that your working example uses an anonymous module it is equivalent to:
module M1
def lab
puts 'm'
end
end
class Foo
include M1
def lab
super
puts 'c'
end
end
Next, think of what class_eval does. It evaluates the given code in the context of the class or module. i.e. it's exactly like you reopened the module and typed the code passed to class_eval. So MyModule = Module.new { class_eval "def lab; puts 'm' end" } is equivalent to
module MyModule
def lab
puts 'm'
end
end
Hopefully this explains the case that works.
When you use instance_eval you are evaluating the code within the context of the receiving object (in this case the instance of module) so MyMod2 = Module.new { instance_eval "def lab; puts 'm' end" } is equivalent to
module MyMod2
def MyMod2.lab
puts 'm'
end
end
i.e. it creates a module method which you'd call via MyMod2.lab and such methods are not added as instance methods by include.
Please note: this answer borrows a bit of its explanation from an answer I wrote to a previous question asking about instance_eval vs. class_eval relating to an example from The Ruby Programming Language book. You might find that answer helpful too.
including a module just takes the instance methods - you are looking for extend. luckily to get the best of both worlds, you can simply do:
module Something
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def blah
puts "lol"
end
end
end
class Test
include Something
end
irb:
>> Test.blah
lol
=> nil
I need to define the constant in the module that use the method from the class that includes this module:
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
But the compiler gives the error on the 4th line.
Is there any other way to define the constant?
The more idiomatic way to achieve this in Ruby is:
module B
def self.included(klass)
klass.class_eval <<-ruby_eval
CONST = find
ruby_eval
# note that the block form of class_eval won't work
# because you can't assign a constant inside a method
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
What you were doing (class << base) actually puts you into the context of A's metaclass, not A itself. The find method is on A itself, not its metaclass. The thing to keep in mind is that classes are themselves objects, and so have their own metaclasses.
To try to make it clearer:
class Human
def parent
# this method is on the Human class and available
# to all instances of Human.
end
class << self
def build
# this method is on the Human metaclass, and
# available to its instance, Human itself.
end
# the "self" here is Human's metaclass, so build
# cannot be called.
end
def self.build
# exactly the same as the above
end
build # the "self" here is Human itself, so build can
# be called
end
Not sure if that helps, but if you don't understand it, you can still use the class_eval idiom above.
In your specific case.
module B
def self.included(base)
base.const_set("CONST", base.find)
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
Despite it works, it's a little bit messy. Are you sure you can't follow a different way to achieve your goal?
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
class << self
def self.find
"AAA"
end
end
include B
end
then the compiler error is fixed, pls try.