module A
def go
p 'go'
end
end
module B
extend self
include A
def start
go
end
end
# doesn't work
# B.start
module C
include A
extend self
def start
go
end
end
# works
C.start
module Constants
HELLO = "Hello!"
end
module D
extend self
include Constants
def start
p HELLO
end
end
# works
D.start
Could someone please explain this to me? How come C.start works if I include A first before extend self? How come the order works if I'm only including Constants?
Any help would be appreciated, thanks.
Why D.start work?
Because extend self adds method start to the singleton class of D. Look the below 2 examples:
without extend self
module Constants
HELLO = "Hello!"
end
module D
#extend self
include Constants
def start
p HELLO
end
end
D.singleton_methods # => []
with extend self
module Constants
HELLO = "Hello!"
end
module D
extend self
include Constants
def start
p HELLO
end
end
D.singleton_methods # => [:start]
why B.start doesn't work ?
This is because you first added the methods of module B to its singleton methods, then included the methods of module A, so #go is not added to the singleton methods of B. Take a look at the below 2 examples.
first include A then extend self.
B.start will work here because, you first included methods from A to B. Then adding all the methods(methods which are defined in B and included from A) of B to the singleton class of B. Thus call to #go is succeed within B.start.
module A
def go
p 'go'
end
end
module B
include A
extend self
def start
go
end
end
B.singleton_methods # => [:start, :go]
first extend self, then include A.
B.start will not work here because, you first extended methods from A to B, then only all the methods from B has been added to the singleton class of B. After that you included methods from A, this is the reason #go not added to the singleton class of B. Thus call to #go is not succeed within B.start.
module A
def go
p 'go'
end
end
module B
extend self
include A
def start
go
end
end
B.singleton_methods # => [:start]
I hope why C.start works is now understood.
Related
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
I know that we can define the included callback for any individual module.
Is there any way to define a callback that is invoked whenever any module gets included in another module or class? The callback would then preferably have access to both the module included, and the class/module where it is included.
I cannot think or find a builtin way in Ruby to do it.
One alternative would be to monkey patch the Module class directly to create the callback. To do it we can add some wrapper methods around the original methods include and extend to force the execution of our defined callbacks each time the include or extend methods are called.
Something along the following lines should work:
class Module
def self.before
m_include = instance_method(:include)
m_extend = instance_method(:extend)
define_method(:include) do |*args, &block|
included_callback(args[0])
m_include.bind(self).call(*args, &block)
end
define_method(:extend) do |*args, &block|
extend_callback(args[0])
m_extend.bind(self).call(*args, &block)
end
end
def included_callback(mod_name)
puts "#{self} now has included Module #{mod_name}"
end
def extend_callback(mod_name)
puts "#{self} now has extended Module #{mod_name}"
end
before
end
An example to test that it works:
module Awesome
def bar
puts "bar"
end
def self.baz
puts "baz"
end
end
class TestIncludeAwesome
include Awesome
end
class TestExtendAwesome
extend Awesome
end
The example code should print as output the following:
> TestIncludeAwesome now has included Module Awesome
> TestExtendAwesome now has extended Module Awesome
class D
def self.callback(mod)
include mod
end
end
module M
def hello
puts 'hi'
end
def self.included(klass)
D.callback(self) unless klass == D
end
end
class C
include M
end
C.new.hello #=> 'hi'
D.new.hello #=> 'hi'
When C includes M, M::included is executed with klass#=>C. Since klass == D is false, D.callback(M) is executed. callback includes M in class D, then M::included is executed with klass#=>D. Since klass == D is now true, M is not included into D a second time.
So in a Ruby class, you can use an idiom such as class << self like the following:
class SalesOrganization
def self.find_new_leads
...
end
class << self
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
add_transaction_tracer :find_new_leads, :category => :task
end
end
My question is what if SalesOrganization is actually a Module instead of Class. Is this doing what I'm hoping it would do, or am I opening up some black magic that I shouldn't be dabbling with?
# Potentially terrible code
module SalesOrganization
def self.find_new_leads
...
end
class << self
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
add_transaction_tracer :find_new_leads, :category => :task
end
end
How do I access a modules singleton class?
No, you're not releasing any black magic. You can define singleton methods on any object, including a module (an instance of the Module class):
module M; end
def M.a
"a"
end
M.a # => "a"
The approaches you suggest work too:
module M
def self.b
"b"
end
end
M.b # => "b"
module M
class << self
def c
"c"
end
end
end
M.c # => "c"
You can also use instance_eval if your method definitions aren't known until runtime:
module M; end
M.instance_eval <<EOF
def d
"d"
end
EOF
M.d # => "d"
Of course, modules like NewRelic... may make assumptions about the classes/modules into which they're included, so you have to be careful there.
I am not sure if I understood what you want to archive. But if you want to write the definition of including C in a module B. And than use C in A after including B, than you can do that this way:
module B
def self.included(base)
base.include C
end
end
class A
include B
# use C
end
That is for your example:
module SalesOrganization
def self.included(base)
base.include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
base.add_transaction_tracer :find_new_leads, :category => :task
end
def self.find_new_leads
...
end
end
If you now include that SalesOrganization module into a class the class will have the Newrelic stuff included.
Consider:
module A
def self.a; puts "a"; end;
def aa; A.a; end;
end
include A
aa
a
aa works but not a. Is there a trick to get A.a available as is a just as include Math makes log available as Math.log? I suspect the way is to write an a method for each self.a method, but is there's a trick to avoid that?
Might you be looking for extend self?
module A
def a; puts "a"; end;
extend self
end
A.a
include A
a
Based on other people's answers, I think what I want is:
module A
module Functions
def a
puts 'a'
end
end
extend Functions
end
# a is a module function of A
A.a
# Explicitly include these functions into one's instance
include A::Functions
a
Now one can include A without polluting their instance space with methods...
unless explicitly doing so with include A::Functions.
There is a trick if you have access to the modules source, and there's still a trick if you don't. If you do, this would be the module A:
module A
def a
puts 'a!'
end
module_function :a
end
All of these will call a:
A.a
include A; a
Even if you don't have access to the module's source, this is still possible with a little (lot) of metaprogramming:
SomeMod.instance_methods(false).each {|method| SomeMod.__send__(:module_function, method.to_sym)}
This only works if the methods are defined as instance methods only in the original module.
If you want do define them as class methods and only make the instance when included:
module A
def self.a
puts 'a'
end
def self.included(klass)
A.singleton_class.instance_methods(false).each do |m|
klass.__send__(:define_method, m.to_sym) do |*args|
A.__send__(m.to_sym, *args)
end
end
end
end
So I am trying out mixins and some metaprogramming in ruby and can't get this to work for me. I want it to print "Baboon"
module A
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def install_A
include InstanceMethods
end
end
module InstanceMethods
class B
def testB
#What goes here???
A.monkey
end
end
attr_accessor :monkey
def initialize()
#monkey = "baboon"
end
def test
b = B.new
puts b.testB
end
end
end
class Chimp
include A
install_A
end
c = Chimp.new
c.test
B is its own self-contained class. It is not associated or connected with any of the other classes or modules except to say that an instance of class B happens to be created inside of InstanceMethods::test. Declaring class B inside of the definition for module InstanceMethods limits the scope of B such that it's not visible outside of InstanceMethods, but it doesn't connect B and InstanceMethods in any way.
What you need is to make the module's variable visible inside B. You can implement an initialize method for B that takes a parameter, and InstanceMethods::test can use b = B.new(#monkey) to pass the value to B (make sure B::initialize stores the value as an instance variable).