Suppose I have
module Mod
def self.included(base)
some_method
end
def self.some_method
one_other_method
end
end
class A < B
include Mod
end
Suppose one_other_method is some method of B class. I get an error message. How can I call one_other_method from Mod without getting this error?
For that you need to change the design a bit :
module Mod
def self.included(base)
some_method(base)
end
def self.some_method(base)
base.new.one_other_method
end
end
class A < B
include Mod
end
Or you could do as
module Mod
def self.included(base)
base.new.some_method
end
def some_method
one_other_method
end
end
class A < B
include Mod
end
The point is - If you define the method as below
def self.some_method(base)
base.new.one_other_method
end
some_method will be only restricted to the module Mod, as module singleton methods are not shared to the class(s)/module(s), where you would include it. That's the reason, you need to think it differently. I don't know your ultimate design goal, so I can't tell you what would be more suitable for you, rather I would say you, these are 2 approaches I am aware of.
Now if you define -
def some_method
one_other_method
end
some_method will be available as instance method to A, and on the other hand as you did class A < B .., one_other_method is also available as instance method to A. Thus, any one of the 2 can call other within them happily, without an explicit receiver, as self will be set by Ruby itself for you.
If you want to have fun, you can also check super_module:
require 'super_module'
module Mod
include SuperModule
def self.some_method
one_other_method
end
some_method
end
class A < B
include Mod
end
Related
Suppose I have
module Mod
def self.included(base)
some_method
end
def some_method
end
end
class A
include Mod
end
I get some_method is not defined. So how can call some_method as soon as Mod is included ?
You have to create a base class instance to call it.
module Mod
def self.included(base)
base.new.some_method
end
def some_method
end
end
class A
include Mod
end
After including module Mod, some_method will be available as instance method of the instances of class A.
The included method is called on class level, when the module is included into a class and some_method is called on class level too. Therefore some_method needs to be a class method to be found. This will work (note the self.some_method):
module Mod
def self.included(base)
some_method
end
def self.some_method
end
end
class A
include Mod
end
Or you need to create an instance of your base class first and call some_method on that instance like #ArupRakshit mentioned in his answer.
As it has been told by spickermann that :included method is called on class level when module is being included into a class, same way :some_method needs to be called on class level too as it is being called from :included method, we can use base.class_eval as class_eval can be used to add methods to a class.
module Mod
def self.included(base)
base.class_eval do
some_method
end
end
def some_method
end
end
class A
include Mod
end
Here you can do the following with :some_method
A.some_method
#=> nil
A.new.some_method
#=> nil
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.
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).
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.