There is a module MyModule:
module MyModule
extend ActiveSupport::Concern
def first_method
end
def second_method
end
included do
second_class_method
end
module ClassMethods
def first_class_method
end
def second_class_method
end
end
end
When some class includes this module, it will have 2 methods exposed as instance methods (first_method and second_method) and 2 class methods (first_class_method and second_class_method) - it is clear.
It is said, that
included block will be executed within the context of the class that
is including the module.
What does it mean exactly? Meaning, when exactly would this method (second_class_method) be executed?
Here's a practical example.
class MyClass
include MyModule
end
When you will include the module in a class, the included hook will be called. Therefore, second_class_method will be called within the scope of Class.
What happens here is
first_method and second_method are included as instance-methods of MyClass.
instance = MyClass.new
instance.first_method
# => whatever returned value of first_method is
The methods of ClassMethods are automatically mixed as class methods of MyClass. This is a common Ruby pattern, that ActiveSupport::Concern encapsulates. The non-Rails Ruby code is
module MyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def this_is_a_class_method
end
end
end
Which results in
MyClass.this_is_a_class_method
or in your case
MyClass.first_class_method
included is a hook that is effectively to the following code
# non-Rails version
module MyModule
def self.included(base)
base.class_eval do
# somecode
end
end
end
# Rails version with ActiveSupport::Concerns
module MyModule
included do
# somecode
end
end
It's mostly "syntactic sugar" for common patterns. What happens in practice, is that when you mix the module, that code is executed in the context of the mixer class.
included is called when you include module into a class, it is used for defining relations, scopes, validations, ...
It gets called before you even have created object from that class.
example
module M
extend ActiveSupport::Concern
...
included do
validates :attr, presence: true
has_many :groups
end
...
end
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
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.
module Test
def self.model_method
puts "this is a module method"
end
end
class A
include Test
end
A.model_method
this will be error with:
undefined method `model_method' for A:Class (NoMethodError)
But when I use metaclass of A. it works:
module Test
def model_method
puts "this is a module method"
end
end
class A
class << self
include Test
end
end
A.model_method
Can someone explain this?
If you want to have both class methods and instance methods mixed into a class when including a module, you may follow the pattern:
module YourModule
module ClassMethods
def a_class_method
puts "I'm a class method"
end
end
def an_instance_method
puts "I'm an instance method"
end
def self.included(base)
base.extend ClassMethods
end
end
class Whatever
include YourModule
end
Whatever.a_class_method
# => I'm a class method
Whatever.new.an_instance_method
# => I'm an instance method
Basically to over-simplify it, you extend to add class methods and you include to add instance methods. When a module is included, it's #included method is invoked, with the actual class it was included in. From here you can extend the class with some class methods from another module. This is quite a common pattern.
See also: http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
Including a module is analogous to copying its instance methods over.
In your example, there are no instance methods to copy to A. model_method is actually an instance method of Test's singleton class.
Given:
module A
def method
end
end
This:
module B
include A
end
Is analogous to this:
module B
def method
end
end
When you think of it this way, this makes perfect sense:
module B
class << self
include A
end
end
B.method
Here, the methods are being copied to the B module's singleton class, which makes them the "class methods" of B.
Note that this is exactly the same thing as:
module B
extend A
end
In reality, the methods are not being copied; there is no duplication. The module is simply included in the method lookup list.
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.