I have a module with methods that write to a log. Into each message I want to put the name of the class that logged this message.
The module can be mixed in using include or extend. I need my log to have correct class names in each case.
Distilled code:
module M
def f
self.class.name
end
end
class C
extend M
include M
end
p C.f # => "Class"
p C.new.f # => "C"
As you see, the first call incorrectly prints "Class". I want it to be "C" as well.
How to accomplish this?
No need to resort to hooks, simply change your behavior when self is a Class/Module:
module M
def f
self.is_a?(Module) ? name : self.class.name
end
end
class C
extend M
include M
end
C.f #=> "C"
C.new.f #=> "C"
You can do it like this, don't know if a better method exists.
module M
def self.included(base)
base.extend ClassMethods
end
def f
self.class.name
end
module ClassMethods
def f
self.name
end
end
end
class C
include M
end
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 have a module like this
module A
module ClassMethods
def a
"a"
end
def b
a
end
end
def self.included(klass)
klass.extend ClassMethods
end
end
I want to factor b into it's own module because Bs are clearly not As. The b method depends on A, and I would like the methods defined in A to be available to B.
Doing this:
module B
module ClassMethods
extend A
def b
a
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
end
It does not work. Nor does include. I tried moving include or extend as well as the how constants are references. What am I missing here?
I hope I understood what you wanted. I split up module A and module B. When I include those in a new class Klass, I have access to both method a and method b, whereas method b from module B depends on method a from module A:
module A
module ClassMethods
def a
"a"
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
end
module B
module ClassMethods
# include A::ClassMethods here and you can do only 'include B' inside Klass
def b
"Called from B: %s" % a
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
end
class Klass
include A, B
end
p Klass.a # => "a"
p Klass.b # => "Called from B: a"
As I understand you can define the same Module in different places, no need to say include, extend or anything. As you see neither Module A nor Module B uses those keywords, they both just define ClassMethods.
Of course, you will have to include module A wherever you use method b of module B or else the method call to a will fail. You can also do include A::ClassMethods inside B::ClassMethods, that way you only need to include B inside Klass.
Add the extend A action in the included callback of the module B instead of adding it in the ClassMethod definition, like the following code does:
module A
module ClassMethods
def a
"a"
end
end
def self.included(klass)
klass.extend ClassMethods
end
end
module B
module ClassMethods
def b
a
end
end
def self.included(klass)
klass.extend(ClassMethods)
klass.extend(A)
end
end
class C
include B
end
C.b
# => "a"
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.
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 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.