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.
Related
How to define an original name scope in module/class with Ruby
I want to implement class like the following:
module SomeModule
extend OriginalNameScope
scope(:some) do
def method1
puts 1
end
def method2
puts 2
end
end
end
class SomeClass
include SomeModule
end
c = SomeClass.new
# I want to call methods like the following:
c.some_method1
c.some_method2
How to implement the OriginalNameScope module? I found out to get the method definitions in this method, but I don't know how to redefine methods with a prefix scope.
module OriginalNameScope
def scope(name, &method_definition)
puts method_definition.class
# => Proc
end
end
This is actually just a combination of some simple standard Ruby metaprogramming patterns and idioms:
module OriginalNameScope
def scope(name)
singleton_class.prepend(Module.new do
define_method(:method_added) do |meth|
if name && !#__recursion_guard__
#__recursion_guard__ = meth
method = instance_method(meth)
undef_method(meth)
define_method(:"#{name}_#{meth}") do |*args, &block|
method.bind(self).(*args, &block)
end
end
#__recursion_guard__ = nil
super(meth)
end
end)
yield
end
end
I just slapped this together, there's probably a lot that can be improved (e.g. use Refinements) and simplified.
module Gym
def self.included(class_or_module)
class_or_module.send(:include, InstanceMethods)
class_or_module.extend(ClassMethods)
end
module ClassMethods
def build
end
end
module InstanceMethods
def open
end
def book_for_practice
end
def close
end
end
end
this is an example in the Ruby's Object Lifecycle Callbacks section of RubyMonk. I don't understand how it's supposed to work or what the point of this is. self.included should just document how the two modules within Gym get used, right? why does class_or_module then get sent/extended? why doesn't it get saved in some sort of arrays that document the lifecyle, like in the examples leading up to this one, such as
##extended_objects = []
def self.extended_objects
##extended_objects
end
def self.extended(class_or_module)
##extended_objects << class_or_module
It's not just documentation. self.included is a callback method that gets called as soon as the module is being included in any other module or class.
Instance methods are included via send, class or module methods via extend in that example.
Find out more in the Ruby documentation.
Let me answer your Question One by one.
1: self.included should just document how the two modules within Gym get used ?
module A
def instance_methods_1
p 'hello instance_methods_1'
end
def instance_methods_2
p 'hello instance_methods_2'
end
module KlassMethods
def klass_methods_1
p 'Hello!! klass_methods_1'
end
def klass_methods_2
p 'Hello!! klass_methods_2'
end
end
end
class B
include A # instead of writing two piece of code like this we could wrap in one using `self.included` method Hook
extend A::KlassMethods
end
B.new.instance_methods_1
B.new.instance_methods_2
B.klass_methods_1
B.klass_methods_2
Another version of same program with method hook using self.included
module A
# this is special method one of the methods hook in ruby.
def self.included(base)
base.extend(KlassMethods)
end
def instance_methods_1
p 'hello instance_methods_1'
end
def instance_methods_2
p 'hello instance_methods_2'
end
module KlassMethods
def klass_methods_1
p 'Hello!! klass_methods_1'
end
def klass_methods_2
p 'Hello!! klass_methods_2'
end
end
end
class B
include A
end
B.new.instance_methods_1
B.new.instance_methods_2
B.klass_methods_1
B.klass_methods_2
2: self.included
To know more about included hook and
Ruby Hook
3: why does class_or_module then get sent/extended ?
module AddAdditionalProperty
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def add_additional_property
p 'ClassMethods::add_additional_property'
end
end
end
module ActiveRecord
class Base
def test
p 'Base test Method'
end
end
end
ActiveRecord::Base.send(:include, AddAdditionalProperty)
ActiveRecord::Base.add_additional_property
## Another version of same Program
module AddAdditionalProperty
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def add_additional_property
p 'ClassMethods::add_additional_property'
end
end
end
module ActiveRecord
class Base
include AddAdditionalProperty
def test
p 'Base test Method'
end
end
end
ActiveRecord::Base.add_additional_property
Hope this answer help you !!!
I'm trying to create a method that passes the caller as the default last argument. According to this, I only need:
class A
def initialize(object = self)
# work with object
end
end
so that in:
class B
def initialize
A.new # self is a B instance here
end
end
self will be B rather than A;
However, this doesn't seem to work. Here's some test code:
class A
def self.test test, t=self
puts t
end
end
class B
def test test,t=self
puts t
end
end
class T
def a
A.test 'hey'
end
def b
B.new.test 'hey'
end
def self.a
A.test 'hey'
end
def self.b
B.new.test'hey'
end
end
and I get:
T.new.a # => A
T.new.b # => #<B:0x000000015fef00>
T.a # => A
T.b # => #<B:0x000000015fed98>
whereas I expect it to be T or #<T:0x000000015fdf08>. Is there a way to set the default last argument to the caller?
EDIT:
class Registry
class << self
def add(component, base=self)
self.send(component).update( base.to_s.split('::').last => base)
end
end
end
The idea is pretty simple, you would use it like this
class Asset_Manager
Registry.add :utilities
end
and you access it like:
include Registry.utilities 'Debugger'
I'm trying to de-couple classes by having a middle-man management type class that takes care of inter-class communications, auto-loading of missing classes and erroring when it doesn't exist, it works but I just want to be able to use the above rather than:
class Asset_Manager
Registry.add :utilities, self
end
It just feels cleaner, that and I wanted to know if such a thing was possible.
You can't escape the explicit self. But you can hide it with some ruby magic.
class Registry
def self.add(group, klass)
puts "registering #{klass} in #{group}"
end
end
module Registrable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def register_in(group)
Registry.add(group, self)
end
end
end
class AssetManager
include Registrable
register_in :utilities
end
# >> registering AssetManager in utilities
In short, you can't.
Ruby resolves the default arguments in the context of the receiver. That is, the object before the . in a method call. What you called the receiver should be the caller, actually.
class A
def test1(value = a)
puts a
end
def test2(value = b)
puts b
end
def a
"a"
end
end
a = A.new
a.test1 #=> a
def a.b; "b" end
a.test2 #=> b
If I were you, I would use the extended (or included) hook, where both the extending class and the extended module can be accessed. You can program what ever logic you want based on the information.
module Registry
module Utilities
def self.extended(cls)
#puts cls
::Registry.send(component).update( cls.to_s.split('::').last => cls)
end
end
end
class Asset_Manager
extend Registry::Utilities
end
In the code below, I would like to call the class method done of the class that includes the module from inside self.hello
Explanation:
A::bonjour will call Mod::hello so will B::ciao
I would like to be able to detect the "calling class" (A or B) in Mod::hello in order to be able to call the A::done or B::done
module Mod
def self.hello
puts "saying hello..."
end
end
class A
include Mod
def self.bonjour
Mod::hello
end
def self.done
puts "fini"
end
end
class B
include Mod
def self.ciao
Mod::hello
end
def self.done
puts "finitto"
end
end
While (perhaps) not as clean as Niklas' answer, it's still easily doable, and IMO cleaner than the usage pattern shown in the OP which relies on knowing which module is mixed in.
(I prefer not having to pass an argument to mixin methods like this when other means exist.)
The output:
pry(main)> A::bonjour
saying hello...
fini
pry(main)> B::ciao
saying hello...
finitto
The guts:
module Mod
module ClassMethods
def hello
puts "saying hello..."
done
end
end
def self.included(clazz)
clazz.extend ClassMethods
end
end
The modified class declarations, removing the explicit module reference:
class A
include Mod
def self.bonjour
hello
end
def self.done
puts "fini"
end
end
class B
include Mod
def self.ciao
hello
end
def self.done
puts "finitto"
end
end
You may also supply a default implementation of done:
module Mod
module ModMethods
def hello
puts "saying hello..."
done
end
def done
throw "Missing implementation of 'done'"
end
end
def self.included(clazz)
clazz.extend ModMethods
end
end
As a comment to this post points out, if the snippet in the OP is a faithful representation of the actual usecase, you might as well use extend (instead of include), leaving everything cleaner:
module Mod
def hello
puts "saying hello..."
done
end
def done
raise NotImplementError("Missing implementation of 'done'")
end
end
And the classes using extend:
class A
extend Mod
def self.bonjour
hello
end
def self.done
puts "fini"
end
end
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.