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
Related
I'd like my module to define new instance methods based on its including class' instance methods. But in the included hook, the class methods are not defined yet (as the module is included at the top of the class, before the class methods are defined):
module MyModule
def self.included(includer)
puts includer.instance_methods.include? :my_class_method # false <- Problem
end
end
class MyClass
include MyModule
def my_class_method
end
end
I want the users of the module to be free to include it at the top of their class.
Is there a way to make a module define additional methods to a class?
Note: I don't have to use the included hook if there is another way to achieve this.
There'a a method_added callback you could use:
module MyModule
def self.included(includer)
def includer.method_added(name)
puts "Method added #{name.inspect}"
end
end
end
class MyClass
include MyModule
def foo ; end
end
Output:
Method added :foo
If you want to track both, existing and future methods, you might need something like this:
module MyModule
def self.on_method(name)
puts "Method #{name.inspect}"
end
def self.included(includer)
includer.instance_methods(false).each do |name|
on_method(name)
end
def includer.method_added(name)
MyModule.on_method(name)
end
end
end
Example:
class MyClass
def foo ; end
include MyModule
def bar; end
end
# Method :foo
# Method :bar
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
I have Ruby class into which I want to include both class and instance methods. Following the pattern described here, I'm currently using the following:
class SomeObject
include SomeObject::Ability
def self.some_builder_method(params)
# use some_class_method ...
end
end
module SomeObject::Ability
module ClassMethods
def some_class_method(param)
# ...
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
def some_instance_method
# ...
end
end
I'd rather not make two separate modules (one being included and the other being extended), because all the methods in my module logically fit together. On the other hand, this pattern a) requires me to define an additional ClassMethods module and b) requires me to write a boilerplate self.included method for every module.
Is there a better way to do this?
Edit 1: I've found another way, but I'm unsure if this is better than the first.
module Concern
def included(base)
# Define instance methods.
instance_methods.each do |m|
defn = instance_method(m)
base.class_eval { define_method(m, defn) }
end
# Define class methods.
(self.methods - Module.methods).each do |m|
unless m == __method__
base.define_singleton_method(m, &method(m))
end
end
end
end
module SomeModule
extend Concern
def self.class_m
puts "Class"
end
def instance_m
puts "Instance"
end
end
class Allo
include SomeModule
end
Allo.class_m # => "Class"
Allo.new.instance_m # => "Instance"
If I understand you correctly, you really just want to use ActiveSupport::Concern:
module PetWorthy
extend ActiveSupport::Concern
included do
validates :was_pet, inclusion: [true, 'yes']
end
def pet #instance method
end
module ClassMethods
def find_petworthy_animal
# ...
end
end
end
class Kitty
include PetWorthy
end
Kitty.find_petworthy_animal.pet
You (hopefully obviously) don't need to use the included method if you don't have any behavior to trigger on include, but I put it in just to demonstrate.
Say there are three classes: A, B & C. I want each class to have a class method, say self.foo, that has exactly the same code for A, B & C.
Is it possible to define self.foo in a module and include this module in A, B & C? I tried to do so and got an error message saying that foo is not recognized.
Yep
module Foo
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def some_method
# stuff
end
end
end
One possible note I should add - if the module is going to be ALL class methods - better off just using extend ModuleName in the Model and defining the methods directly in the module instead - rather than having a ClassMethods module inside the Module, a la
module ModuleName
def foo
# stuff
end
end
module Common
def foo
puts 'foo'
end
end
class A
extend Common
end
class B
extend Common
end
class C
extend Common
end
A.foo
Or, you can extend the classes afterwards:
class A
end
class B
end
class C
end
[A, B, C].each do |klass|
klass.extend Common
end
Rails 3 introduced a module named ActiveSupport::Concern which has the goal of simplifying the syntax of modules.
module Foo
extend ActiveSupport::Concern
module ClassMethods
def some_method
# stuff
end
end
end
It allowed us to save a few lines of "boilerplate" code in the module.
This is basic ruby mixin functionality that makes ruby so special.
While extend turns module methods into class methods, include turns module methods into instance methods in the including/extending class or module.
module SomeClassMethods
def a_class_method
'I´m a class method'
end
end
module SomeInstanceMethods
def an_instance_method
'I´m an instance method!'
end
end
class SomeClass
include SomeInstanceMethods
extend SomeClassMethods
end
instance = SomeClass.new
instance.an_instance_method => 'I´m an instance method!'
SomeClass.a_class_method => 'I´m a class method'
Just wanted to extend Oliver's answer
Define Class methods and instance methods together in a module.
module Foo
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def a_class_method
puts "ClassMethod Inside Module"
end
end
def not_a_class_method
puts "Instance method of foo module"
end
end
class FooBar
include Foo
end
FooBar.a_class_method
FooBar.methods.include?(:a_class_method)
FooBar.methods.include?(:not_a_class_method)
fb = FooBar.new
fb.not_a_class_method
fb.methods.include?(:not_a_class_method)
fb.methods.include?(:a_class_method)
I have a Mix-in that reflects on the receiver class to generate some code. This means that I need to execute the class method at the end of the class definition, like in this trivially dumbed down example:
module PrintMethods
module ClassMethods
def print_methods
puts instance_methods
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
class Tester
include PrintMethods
def method_that_needs_to_print
end
print_methods
end
I'd like to have the mixin do this for me automatically, but I can't come up with a way. My first thought was to add receiver.print_methods to self.included in the mixin, but that won't work because the method that I want it to reflect on has not been declared yet. I could call include PrintMethods at the end of the class, but that feels like bad form.
Are there any tricks to make this happen so I don't need to call print_methods at the end of the class definition?
First of all, there's no end of class definition. Remember that in Ruby you can reopen the Tester class method after you have 'initialized' it, so the interpreter can't know where the class 'ends'.
The solution I can come up with is to make the class via some helper method, like
module PrintMethods
module ClassMethods
def print_methods
puts instance_methods
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
class Object
def create_class_and_print(&block)
klass = Class.new(&block)
klass.send :include, PrintMethods
klass.print_methods
klass
end
end
Tester = create_class_and_print do
def method_that_needs_to_print
end
end
But certainly having to define classes this way makes my eyes hurt.