I love ActiveSupport::Concern.
It makes it easy to add functionality to your classes, with a nice syntax.
Anyways, in Rails 3.2, the InstanceMethods module has been deprecated. If I understood correctly, we should just define our methods in the included block (actually it's just in the body of the module):
# edit: don't do this! The method definition should just be in the body of the module
included do
def my_method; end
end
I was just wondering if anyone knows why they decided to do that?
Let's look at the example you linked first.
module TagLib
extend ActiveSupport::Concern
module ClassMethods
def find_by_tags()
# ...
end
end
module InstanceMethods
def tags()
# ...
end
end
end
When you include TagLib into your class AS Concern automatically extends the class with ClassMethods module and includes InstanceMethods module.
class Foo
include TagLib
# is roughly the same as
include TagLib::InstanceMethods
extend TagLib::ClassMethods
end
But as you may noticed we are already including TagLib module itself so the methods defined within it are already available as instance methods on the class. Why would you want to have a separate InstanceMethods module then?
module TagLib
extend ActiveSupport::Concern
module ClassMethods
def find_by_tags()
# ...
end
end
def tags()
# ...
end
end
class Foo
include TagLib
# does only `extend TagLib::ClassMethods` for you
end
Related
I'm trying to better my understanding of meta-programming in Ruby and am confused as to what Module.included is? My current understanding is that this is a callback invoked by Ruby whenever the module is included into another module or class. Other than that, what types of (meta-)programming constructs are these used in? Any examples?
Module#included allows modules to inject both class and instance methods, and execute associated code, from a single include.
The documentation for ActiveSupport::Concern illustrates a typical use. It's injecting class methods into the calling class, and executing code. In this case, adding a scope.
module M
def self.included(base)
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end
module ClassMethods
...
end
end
And here's the ActiveSupport::Concern version which does the same thing, but adds declarative syntax sugar.
require 'active_support/concern'
module M
extend ActiveSupport::Concern
included do
scope :disabled, -> { where(disabled: true) }
end
class_methods do
...
end
end
With included a class simply includes the module. It allows the module to be one neat package: instance methods, class methods, and setup code.
class Thing
# Class and instance methods are injected, and the new scope is added.
include M
end
Without included a module can only inject instance methods. Class methods would have to be added separately, as well as executing any setup code.
module M
def some_instance_method
...
end
module ClassMethods
def setup
scope :disabled, -> { where(disabled: true) }
end
end
end
class Thing
# Inject the instance methods
include M
# Inject the class methods
extend M::ClassMethods
# Run any setup code.
setup
end
Other examples might be registering a class, for example as an available plugin.
module Plugin
def self.included(base)
base.extend ClassMethods
base.class_eval do
register_as_plugin(base)
end
end
module ClassMethods
def register_as_plugin(klass)
...
end
end
end
class Thing
include Plugin
end
Or adding necessary accessors.
module HasLogger
def self.included(base)
base.class_eval do
attr_writer :logger
end
end
def logger
#logger ||= Rails.logger
end
end
class Thing
include HasLogger
end
I'm trying to extend an existing Concern in a different Rails project.
This module exists in a gem I'm requiring:
module Foo
extend ActiveSupport::Concern
included do
#some stuff
end
def method_a
end
end
And then in my project:
module Foo
extend ActiveSupport::Concern
included do
#some other stuff
end
def method_b
end
end
Result is, objects including Foo only have method_b, and only run #some other stuff on inclusion. Is there any way for all code under included to run, and all methods to be added?
EDIT: Both the gem and the project are mine, and I'm not dead set on using ActiveSupport::Concern if there's a more fitting solution.
You shouldn't override or extend directly the concern. With a simple module it would be maybe useful, but concerns are set up to be explicitly extended:
module MyFoo
extend ActiveSupport::Concern
extend Foo
included do
#some other stuff
end
def method_b
end
end
I want to define a method encapsulated module.
It has the same name as a method in a different module.
When calling the one I want it calls the other one that is mixed in first.
Code :
class User
include ModuleA
include ModuleB
end
module ModuleA
extend ActiveSupport::Concern
included do
def hi
end
end
end
module ModuleB
extend ActiveSupport::Concern
def hi(param)
end
def say_hi
hi(param)
end
end
Errors with
ArgumentError: wrong number of arguments (1 for 0)
# ./app/models/concerns/modulea.rb:16:in `hi'
Rails 4
The correct answer would be to remove included in both the Modules.
Instance methods will happily be included without the included block. as suggested by #Sergio Tulentsev
class User
include ModuleA
include ModuleB
end
module ModuleA
extend ActiveSupport::Concern
def hi
end
end
module ModuleB
extend ActiveSupport::Concern
def hi(param)
end
def say_hi
hi(param)
end
end
I want to define a class and let many helpers use.
I can include MvaasPortal moude in fine,
Then I can new the object , but can not use any methods of the object,
It's so strange.
If I can not use the methods in the object, why I can new the object.
Ruby is so strange.
#portal = Portal.new
There is no methods in #portal object
mvaas_portal.rb
module MvaasPortal
module InstanceMethods
class Portal
def initialize(server_url)
~~~~
end
def query_server(body_to_send={},session_id=nil)
~~~
end
end
end
def self.included(receiver)
receiver.send :include, InstanceMethods
end
end
If you're using rails, you can use ActiveSupport::Concern : http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
If don't, take a look at the first example on the link.
Moreover, your namespace is a little bit weird and misses some context. Here is an example with a dummy method :
require 'active_support/concern'
module MvaasPortal
include ActiveSupport::Concern
def an_instance_method
puts "Here!"
end
end
class Portal
include MvaasPortal
end
Portal.new.an_instance_method
=> "Here!"
I want to dynamically generate a class method in a Mixin, based on the class name that include this Mixin.
Here is my current code:
module MyModule
extend ActiveSupport::Concern
# def some_methods
# ...
# end
module ClassMethods
# Here is where I'm stuck...
define_method "#{self.name.downcase}_status" do
# do something...
end
end
end
class MyClass < ActiveRecord::Base
include MyModule
end
# What I'm trying to achieve:
MyClass.myclass_status
But this give me the following method name:
MyClass.mymodule::classmethods_status
Getting the base class name inside the method definition works (self, self.name...) but I can't make it works for the method name...
So far, I've tried
define_method "#{self}"
define_method "#{self.name"
define_method "#{self.class}"
define_method "#{self.class.name}"
define_method "#{self.model_name}"
define_method "#{self.parent.name}"
But none of this seems to do the trick :/
Is there any way I can retrieve the base class name (not sure what to call the class that include my module). I've been struggling with this problem for hours now and I can't seem to figure out a clean solution :(
Thanks!
I found a clean solution: using define_singleton_method (available in ruby v1.9.3)
module MyModule
extend ActiveSupport::Concern
included do
define_singleton_method "#{self.name}_status" do
# do stuff
end
end
# def some_methods
# ...
# end
module ClassMethods
# Not needed anymore!
end
end
You can't do it like that - at this point it is not yet known which class (or classes) are including the module.
If you define a self.included method it will be called each time the module is included and the thing doing the including will be passed as an argument. Alternatively since you are using AS::Concern you can do
included do
#code here is executed in the context of the including class
end
You can do something like this:
module MyModule
def self.included(base)
(class << base; self; end).send(:define_method, "#{base.name.downcase}_status") do
puts "Hey!"
end
base.extend(ClassMethods)
end
module ClassMethods
def other_method
puts "Hi!"
end
end
end
class MyClass
include MyModule
end
MyClass.myclass_status
MyClass.other_method
Works for extend:
module MyModule
def self.extended who
define_method "#{who.name.downcase}_status" do
p "Inside"
end
end
end
class MyClass
extend MyModule
end
MyClass.myclass_status