Module Baz
def foo
super
:baz
end
end
Class A
prepend Baz
def foo
:bar
end
end
A.new.foo //works fine
now if I transform my module to Concern module, it's not...
module BazConcern
extend ActiveSupport::Concern
included do
def foo
super
:baz
end
end
end
So how can we use prepend with ActiveSupport::Concern ? with ruby 2+
prepend with ActiveSupport::Concern (Rails 6.1+)
Rails 6.1 added support of prepend with ActiveSupport::Concern.
Please, see the following example:
module Imposter
extend ActiveSupport::Concern
# Same as `included`, except only run when prepended.
prepended do
end
end
class Person
prepend Imposter
end
It is also worth to mention that concerning is also updated:
class Person
concerning :Imposter, prepend: true do
end
end
Sources:
A link to the corresponding commit.
Rails allows a module with extend ActiveSupport::Concern to be prepended.
prepend and concerning docs.
It looks like there is a version of ActiveSupport::Concern that supports prepending available here: https://gist.github.com/bcardarella/5735987.
I haven't tried it out yet, but I might some day.
(Linked to from https://groups.google.com/forum/#!topic/rubyonrails-core/sSk9IEW74Ro)
Related
I have to wrap some behavior around an external gem in a elegant and isolated manner. Given the abstraction below, everything runs smoothly, but 'bar' is never printed.
Could someone tell me why?
My code:
module RefineGem
refine GemMainModule::GemClass do
def self.foo
p 'bar'
super
end
end
end
module Test
using RefineGem
def test
GemMainModule::GemClass.foo
end
end
class Testing
include Test
end
Testing.new.test
Gem code:
module GemMainModule
class Base
include GemMainModule::Fooable
end
class GemClass < Base
end
end
module GemMainModule
module Fooable
extend ActiveSupport::Concern
class_methods do
def foo
p 'zoo'
end
end
end
end
I doubt refinements work for class methods. You might refine the singleton_class though:
module RefineGem
refine GemMainModule::GemClass.singleton_class do
def foo
p 'bar'
super
end
end
end
I personally prefer to use Module#prepend to achieve the same functionality:
GemMainModule::GemClass.singleton_class.prepend(Module.new do
def foo
p 'bar'
super
end
end)
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
This question directly relates to this one. But I tried to break it down to the base problem and I didn't want to enter even more text into the other question box. So here goes:
I know that I can include classmethods by extending the module ClassMethods and including it via the Module#include hook. But can I do the same with prepend? Here is my example:
class Foo:
class Foo
def self.bar
'Base Bar!'
end
end
class Extensions:
module Extensions
module ClassMethods
def bar
'Extended Bar!'
end
end
def self.prepended(base)
base.extend(ClassMethods)
end
end
# prepend the extension
Foo.send(:prepend, Extensions)
class FooE:
require './Foo'
class FooE < Foo
end
and a simple startscript:
require 'pry'
require './FooE'
require './Extensions'
puts FooE.bar
When I start the script I don't get Extended Bar! like I expect but rather Base Bar!. What do I need to change in order to work properly?
A simpler version:
module Extensions
def bar
'Extended Bar!'
end
end
Foo.singleton_class.prepend Extensions
The problem is that even though you're prepending the module, ClassMethods is still getting extended in. You could do this to get what you want:
module Extensions
module ClassMethods
def bar
'Extended Bar!'
end
end
def self.prepended(base)
class << base
prepend ClassMethods
end
end
end
Note that Extensions itself could be either prepended or included in Foo. The important part is prepending ClassMethods.
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.
possibly I'm not explaining the concept very well, but I'm looking to add class methods to a series of ruby classes to enable them to hold class specific information which will then be called by individual instance methods of the classes.
I can make it work, but it is a bit ugly. Can anyone as it requires 2 modules, one included and the other extended (see example code below).
Can anyone think of a more elegant way of implementing this functionality ?
Thanks
Steve
This module is extended to give class methods but adding an instance member to each class it is included in
module My1
def my_methods (*sym_array)
#my_methods=sym_array
end
def method_list
#my_methods
end
end
This module is included to give instance methods
module My2
def foo
self.class.method_list.each { |m| self.send m }
end
end
Now use the modules - the ugliness is having to use an include and extend statement to allow me to pass a set of symbols to a class method which will then be implemented in an
instance
class Foo
extend My1
include My2
my_methods :baz
def baz
puts "Baz!"
end
end
class Bar
extend My1
include My2
my_methods :frodo
def frodo
puts "Frodo!"
end
end
class Wibble < Bar
extend My1
include My2
my_methods :wobble
def wobble
puts "Wobble!"
end
end
Here is the required output - note that each class has its own instance #my_methods so the behaviour is different for the derived class Wibble < Bar
f=Foo.new
b=Bar.new
w=Wibble.new
f.foo #=> "Bar!"
b.foo #=> "Frodo!"
w.foo #=> "Wobble!"
When a module is included, a hook is called on it. You can use that to do the extend you want.
module M1
def self.included(base)
base.extend(M2)
end
end
People often call that second module M1::ClassMethods. If you're using rails, ActiveSupport::Concern encapsulates this pattern
I would suggest to use a hook from module instead:
module MyModule
def self.included(klass)
klass.extend ClassMethods
end
def foo
self.class.method_list.each{ |m| self.send m }
end
module ClassMethods
attr_reader :method_list
def my_methods(*sym_array)
#method_list = sym_array
end
end
end
So it simplifies to call include only a module whenever you want the functionality to given classes.
class Foo
include MyModule
my_methods :baz
def baz
puts "Baz!"
end
end