I have concern in which I store constants:
module Group::Constants
extend ActiveSupport::Concern
MEMBERSHIP_STATUSES = %w(accepted invited requested
rejected_by_group rejected_group)
end
And another concern that I wish to use these constants:
module User::Groupable
extend ActiveSupport::Concern
include Group::Constants
MEMBERSHIP_STATUSES.each do |status_name|
define_method "#{status_name}_groups" do
groups.where(:user_memberships => {:status => status_name})
end
end
end
Unfortunately, this results in a routing error:
uninitialized constant User::Groupable::MEMBERSHIP_STATUSES
It looks like the first concern isn't loading properly in the second concern. If that's the case, what can I do about it?
It appears this behavior is by design, as explained nicely over here.
What you'll need to do in this case is not have Group::Constants extend from ActiveSupport::Concern since that will block its implementation from being shared with other ActiveSupport::Concern extending modules (although it will be ultimately shared in a class that includes the second module):
module A
TEST_A = 'foo'
end
module B
extend ActiveSupport::Concern
TEST_B = 'bar'
end
module C
extend ActiveSupport::Concern
include A
include B
end
C::TEST_A
=> 'foo'
C::TEST_B
=> uninitialized constant C::TEST_B
class D
include C
end
D::TEST_A
=> 'foo'
D::TEST_B
=> 'bar'
In short, you'll need to make Group::Constants a standard module and then all will be well.
If you want to keep everything in one file, and if you can stomach a bit of boilerplate, you could break up your module into a "concern" bit and a "non-concern" bit:
module A
FOO = [22]
def self.included base
base.include Concern
end
module Concern
extend ActiveSupport::Concern
class_methods do
def foo_from_the_perspective_of_a_class_method_in_A
{lexical: FOO, instance: self::FOO}
end
end
end
end
module B
extend ActiveSupport::Concern
include A
FOO += [33]
def foo_from_the_perspective_of_an_instance_method_in_B
FOO
end
end
class C
include B
end
C.foo_from_the_perspective_of_a_class_method_in_A
=> {:lexical=>[22], :instance=>[22, 33]}
C.new.foo_from_the_perspective_of_an_instance_method_in_B
=> [22, 33]
C::FOO
=> [22, 33]
Related
I am extending ActiveRecord::Base. In my lib folder, I have a file mongoid_bridge.rb:
module MongoidBridge
extend ActiveSupport::Concern
module ClassMethods
...
end
module InstanceMethods
...
def create_mongo(klass, fields)
...
end
end
end
ActiveRecord::Base.send(:include, MongoidBridge)
In config/initializers, I have two files, in order to be read in the correct order, each is prefixed with 01, 02, etc. In 01_mongo_mixer.rb, I have the following:
require "active_record_bridge"
require "mongoid_bridge"
Then in 02_model_initializer.rb, I have the following:
MyActiveRecordModel.all.each do |model|
model.create_mongo(some_klass, some_fields)
end
model is an instance of an ActiveRecord subclass, so it should find create_mongo instance method in the lookup chain. However, it does not find it, as I get the following error:
Uncaught exception: undefined method `create_mongo' for #<MyActiveRecordModel:0x007fff1f5e5e18>
Why can't it find the instance method?
UPDATE:
It seems that methods under ClassMethods are included, but not the methods under InstanceMethods:
singleton_respond = MyActiveRecordModel.respond_to? :has_many_documents
# => true
instance_respond = MyActiveRecordModel.new.respond_to? :create_mongo
# => false
You don't need an InstanceMethods module - your module should look like
module MongoidBridge
extend ActiveSupport::Concern
module ClassMethods
...
end
def create_mongo
end
end
Earlier versions of rails made use of an instance methods module but in the end it was decided that this was redundant since you could just define the methods in the enclosing module. Using InstanceMethods was deprecated a while back (maybe rails 3.2 - my memory is fuzzy) and subsequently removed
Expanding on my question here (ruby/rails: extending or including other modules), using my existing solution, what's the best way to determine if my module is included?
What I did for now was I defined instance methods on each module so when they get included a method would be available, and then I just added a catcher (method_missing()) to the parent module so I can catch if they are not included. My solution code looks like:
module Features
FEATURES = [Running, Walking]
# include Features::Running
FEATURES.each do |feature|
include feature
end
module ClassMethods
# include Features::Running::ClassMethods
FEATURES.each do |feature|
include feature::ClassMethods
end
end
module InstanceMethods
def method_missing(meth)
# Catch feature checks that are not included in models to return false
if meth[-1] == '?' && meth.to_s =~ /can_(\w+)\z?/
false
else
# You *must* call super if you don't handle the method,
# otherwise you'll mess up Ruby's method lookup
super
end
end
end
def self.included(base)
base.send :extend, ClassMethods
base.send :include, InstanceMethods
end
end
# lib/features/running.rb
module Features::Running
module ClassMethods
def can_run
...
# Define a method to have model know a way they have that feature
define_method(:can_run?) { true }
end
end
end
# lib/features/walking.rb
module Features::Walking
module ClassMethods
def can_walk
...
# Define a method to have model know a way they have that feature
define_method(:can_walk?) { true }
end
end
end
So in my models I have:
# Sample models
class Man < ActiveRecord::Base
# Include features modules
include Features
# Define what man can do
can_walk
can_run
end
class Car < ActiveRecord::Base
# Include features modules
include Features
# Define what man can do
can_run
end
And then I can
Man.new.can_walk?
# => true
Car.new.can_run?
# => true
Car.new.can_walk? # method_missing catches this
# => false
Did I write this correctly? Or is there a better way?
If I understand your question correctly, you can use Module#include?:
Man.include?(Features)
For example:
module M
end
class C
include M
end
C.include?(M) # => true
Other ways
Checking Module#included_modules
This works, but it's a bit more indirect, since it generates intermediate included_modules array.
C.included_modules.include?(M) # => true
since C.included_modules has a value of [M, Kernel]
Checking Module#ancestors
C.ancestors.include?(M) #=> true
since C.ancestors has a value of [C, M, Object, Kernel, BasicObject]
Using operators like <
The Module class also declares several comparison operators:
Module#<
Module#<=
Module#==
Module#>=
Module#>
Example:
C < M # => true
I am using Ruby 1.9.2 and the Ruby on Rails v3.2.2 gem. I would like to "nest" the inclusion of modules given I am using the RoR ActiveSupport::Concern feature, but I have a doubt where I should state the include method. That is, I have the following:
module MyModuleA
extend ActiveSupport::Concern
# include MyModuleB
included do
# include MyModuleB
end
end
Should I state include MyModuleB in the "body" / "context" / "scope" of MyModuleA or I should state that in the included do ... end block? What is the difference and what I should expect from that?
If you include MyModuleB in the "body" of MyModuleA, then it is the module itself that is extended with B's functionality. If you include it in the included block, then it is included on the class that mixes in MyModuleA.
That is:
module MyModuleA
extend ActiveSupport::Concern
include MyModuleB
end
produces something like:
MyModuleA.send :include, MyModuleB
class Foo
include MyModuleA
end
while
module MyModuleA
extend ActiveSupport::Concern
included do
include MyModuleB
end
end
produces something like:
class Foo
include MyModuleA
include MyModuleB
end
The reason for this is that ActiveSupport::Concern::included is analogous to:
def MyModuleA
def self.included(klass, &block)
klass.instance_eval(&block)
end
end
The code in the included block is run in the context of the including class, rather than the context of the module. Thus, if MyModuleB needs access to the class it's being mixed-in to, then you'd want to run it in the included block. Otherwise, it's effectively the same thing.
By means of demonstration:
module A
def self.included(other)
other.send :include, B
end
end
module B
def self.included(other)
puts "B was included on #{other.inspect}"
end
end
module C
include B
end
class Foo
include A
end
# Output:
# B was included on C
# B was included on Foo
I have the following:
module Thing
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
attr_reader :things
def has_things(*args)
options = args.extract_options! # Ruby on Rails: pops the last arg if it's a Hash
# Get a list of the things (Symbols only)
#things = args.select { |p| p.is_a?(Symbol) }
include InstanceMethods
end
end
module InstanceMethods
self.class.things.each do |thing_name| # !!! Problem is here, explaination below
define_method "foo_for_thing_#{thing_name}" do
"bar for thing #{thing_name}"
end
end
end
end
In another class which mixes-in the Thing module:
class Group
has_things :one, :two, :option => "something"
end
When calling has_things within a class, I would like to have the dynamic "foo_for_thing_one" and "foo_for_thing_two" instance methods available. For example:
#group = Group.new
#group.foo_for_thing_one # => "bar for thing one"
#group.foo_for_thing_two # => "bar for thing two"
However, I get the following error:
`<module:InstanceMethods>': undefined method `things' for Module:Class (NoMethodError)
I realize that "self" in the problem line pointed out above (first line of the InstanceMethods module) refers to the InstanceMethods module.
How do I reference the "things" class method (which returns [:one, :two] in this example) so I can loop through and create dynamic instance methods for each? Thanks. Or if you have other suggestions for accomplishing this, please let me know.
Quick answer:
Put the contents of InstanceMethods inside the has_things method definition and remove the InstanceMethods module.
Better answer:
Your use of the InstanceMethods-ClassMethods anti-pattern is especially unwarranted here and cargo-culting it has added to your confusion about scope and context. Do the simplest thing that could possibly work. Don't copy someone else's code without critical thinking.
The only module you need is ClassMethods, which should be given a useful name and should not be included but rather used to extend the class that you want to grant the has_things functionality. Here's the simplest thing that could possibly work:
module HasThings
def has_things(*args)
args.each do |thing|
define_method "thing_#{thing}" do
"this is thing #{thing}"
end
end
end
end
class ThingWithThings
extend HasThings
has_things :foo
end
ThingWithThings.new.thing_foo # => "this is thing foo"
Only add complexity (options extraction, input normalization, etc) when you need it. Code just in time, not just in case.
Say there are 3 models: A, B, and C. Each of these models has the x attribute.
Is that possible to define a named scope in a module and include this module in A, B, and C ?
I tried to do so and got an error message saying that scope is not recognized...
Yes it is
module Foo
def self.included(base)
base.class_eval do
scope :your_scope, lambda {}
end
end
end
As of Rails 3.1 the syntax is simplified a little by ActiveSupport::Concern:
Now you can do
require 'active_support/concern'
module M
extend ActiveSupport::Concern
included do
scope :disabled, where(:disabled => true)
end
module ClassMethods
...
end
end
ActiveSupport::Concern also sweeps in the dependencies of the included module, here is the documentation
[update, addressing aceofbassgreg's comment]
The Rails 3.1 and later ActiveSupport::Concern allows an include module's instance methods to be included directly, so that it's not necessary to create an InstanceMethods module inside the included module. Also it's no longer necessary in Rails 3.1 and later to include M::InstanceMethods and extend M::ClassMethods. So we can have simpler code like this:
require 'active_support/concern'
module M
extend ActiveSupport::Concern
# foo will be an instance method when M is "include"'d in another class
def foo
"bar"
end
module ClassMethods
# the baz method will be included as a class method on any class that "include"s M
def baz
"qux"
end
end
end
class Test
# this is all that is required! It's a beautiful thing!
include M
end
Test.new.foo # ->"bar"
Test.baz # -> "qux"
As for Rails 4.x you can use gem scopes_rails
It can generate scopes file and include it to your model.
Also, it can automatically generate scopes for state_machines states.