I have 2 modules and 1 class:
module B
def hi
say 'hi'
end
end
module C
def say(message)
puts "#{message} from ???"
end
end
class A
include B
include C
end
A.new.hi
#=> hi from ???"
How i can get message as hi from B?
You could use caller_locations to determine the calling method's name, and use that information to retrieve the method's owner:
module C
def say(message)
method_name = caller_locations(1, 1)[0].base_label
method_owner = method(method_name).owner
puts "#{message} from #{method_owner}"
end
end
But this is very brittle. It would be much easier to simply pass the calling module, e.g.:
module B
def hi
say 'hi', B
end
end
module C
def say(message, mod)
puts "#{message} from #{mod}"
end
end
You can use Kenel#__method__ with Method#owner:
module C
def say(message)
puts "#{message} from #{method(__method__).owner }"
end
end
class A
include C
end
A.new.say('hello')
#=> hello from C
Kenel#__method__:
Returns the name at the definition of the current method as a Symbol.
If called outside of a method, it returns nil.
Method#owner:
Returns the class or module that defines the method.
Related
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.
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
I have a module M which I want to tag specific methods as "special", in such a way that classes which mix in this module can check whether a given method name is special. This is what I've tried:
module M
def specials
#specials ||= {}
end
def self.special name
specials[name] = true
end
def is_special? name
specials[name]
end
def meth1
...
end
special :meth1
end
class C
include M
def check name
is_special? name
end
end
Of course this doesn't work, because I can't call an instance method from the class method self.special. I suspect that if I want to keep the feature of being able to call special :<name> below the wanted methods in the module, I have no choice but to use class variables (e.g. ##specials) Can somebody prove me wrong?
You can make all these methods class methods and do the following:
module M
def self.specials
#specials ||= {}
end
def self.special name
self.specials[name] = true
end
def self.is_special? name
self.specials[name]
end
def meth1
'foo'
end
def meth2
'bar'
end
special :meth1
end
class C
include M
def check name
M.is_special? name
end
end
p C.new.check(:meth1)
#=> true
p C.new.check(:meth2)
#=> nil
Not sure if that would work for you.
I want to override a method from a module A from another module B that will monkey-patch A.
http://codepad.org/LPMCuszt
module A
def foo; puts 'A' end
end
module B
def foo; puts 'B'; super; end
end
A.module_eval { include B } # why no override ???
class C
include A
end
# must print 'A B', but only prints 'A' :(
C.new.foo
module A
def foo
puts 'A'
end
end
module B
def foo
puts 'B'
super
end
end
include A # you need to include module A before you can override method
A.module_eval { include B }
class C
include A
end
C.new.foo # => B A
Including a module places it above the module/class that is including it in the class hierarchy. In other words, A#foo is not super of B#foo but rather the other way round.
If you think of including a module as a way of doing multiple inheritance this makes sense, include SomeModule is a way of saying, "Treat SomeModule like it is a parent class for me".
To get the output you wanted you need to reverse the inclusion so that B includes A:
module A
def foo; puts 'A' end
end
module B
def foo; puts 'B'; super; end
end
B.module_eval { include A } # Reversing the inclusion
class C
include B # not include A
end
puts C.new.foo
Edit in response to comment:
Then either include both A and B in C with B included after A:
# A and B as before without including B in A.
class C
include A
include B
end
or patch A in C itself and don't bother with B.
# A as before, no B.
class C
include A
def foo; puts 'B'; super; end
end
The only way for this to work is if the method lookup on C is C -> B -> A and there is no way to do this without including B into C.
This is also one solution to your question. I am trying to achieve with module hooks included. When you include the module A into class C. included callbacks defined in module A is called and executed. We included the module B on-fly. So our module A method foo is overridden by Module B foo to print the superclass module method just called super.
module A
def self.included klass
klass.send(:include, B)
end
def foo
puts 'A'
end
end
module B
def foo
super
puts 'B'
end
end
class C
include A
end
C.new.foo #out put A,B
Another way to accomplish this is to include module B when module A is included.
module A
def foo
puts "this should never be called!"
"a"
end
end
module B
def foo
"b"
end
end
module A
def self.included(base)
base.class_eval do
include B
end
end
end
class C
include A
end
C.new.foo # "b"
I have the following program.
module C
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def test_for
class_eval <<-DEFINECLASSMETHODS
def self.my_method(param_a)
puts "SELF is: #{self.inspect}"
puts param_a
puts "#{param_a}"
end
DEFINECLASSMETHODS
end
end
end
class A
include C
end
class B < A
test_for
end
when I run B.new.my_method("aaa"), I got this error
NameError: undefined local variable or method `param_a' for B:Class
I am quite confused.
I define param_a as a local variable in class method my_method,
puts param_a
runs good, and will output the "aaa".
however,
puts "#{param_a}"
output that error.
why?
Can anyone explain this?
You get that error because the #{} doesn't interpolate param_a into the string passed to puts - it interpolates it into the string passed to class_eval. It will work when you escape it, i.e.
puts "\#{param_a}"
You can also disable interpolation inside the heredoc by using <<-'DEFINECLASSMETHODS' instead of <<-DEFINECLASSMETHODS. This will also allow you to use other meta characters without having to escape them.
Try using "class_eval do; end" instead, like this:
def test_for
class_eval do
def self.my_method(param_a)
puts "SELF is: #{self.inspect}"
puts param_a
puts "#{param_a}"
end
end
end
This way, no code escaping is necessary.
Those are some majorly complex hoops you are jumping through, to achieve basically this:
module C
def test_for
define_singleton_method :my_method do |param_a|
puts "SELF is: #{inspect}"
p param_a
end
end
end
class A
extend C
end
class B < A
test_for
end
B.my_method 'foo'
# => SELF is: B
# => "foo"
EDIT: I just realized that the solution above is still much more complicated than it needs to be. In fact, we do not need any metaprogramming at all:
module C
module D
def my_method(param_a)
puts "SELF is: #{inspect}"
p param_a
end
end
def test_for
extend D
end
end
class A
extend C
end
class B < A
test_for
end
B.my_method 'foo'
# => SELF is: B
# => "foo"