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
Related
I have defined a method in module A. I want to call the same method from the moduleB
module A
included do
def self.some_func
end
end
end
module B
some_func # It raise error (NoMethodError: undefined method). How solve this?
end
module C
include A
include B
end
This doesn't work. Is it possible to call a function that is defined by one module in another module?
That module A should be raising an ArgumentError unless it also has an extends ActiveSupport::Concern at the top. Without ActiveSupport::Concern, you'd be calling the Module#included instance method here:
included do
...
end
but that requires an argument.
If you say this:
module A
extend ActiveSupport::Concern
included do
def self.some_func
end
end
end
then you'll get the included you're trying to use and the module A that you're expecting.
Also, module B doesn't include A so it has nowhere to get some_func from so this:
module B
some_func
end
will give you a NoMethodError. If you include A:
module B
include A
some_func
end
then it will work.
I'm creating an ActiveSupport::Concern that defines several class methods using the class_methods method. With a regular module it is possible to invoke the class methods directly using NameOfModule.target_method (e.g. in the stdlib class Math it's common to invoke acos like so Math.acos(x)) but I'm at a loss finding how to perform a similar invocation my Concern. Is this possible, if so, how?
No, you can't because the methods defined in the block of class_methods are actually defined in the module Foo::ClassMethods (Foo is your concern). Here is the relevant source code of ActiveSupport::Concern
module ActiveSupport
# ...
module Concern
# ...
def class_methods(&class_methods_module_definition)
mod = const_defined?(:ClassMethods, false) ?
const_get(:ClassMethods) :
const_set(:ClassMethods, Module.new)
mod.module_eval(&class_methods_module_definition)
end
end
end
You can see that class_methods just creates the module ClassMethods for you if it is not defined by yourself. The methods you defined are just instance methods in that module, so you can't call it at the module level.
Later on, the module ClassMethods will be extended by the class that includes your concern. Here is the relevant source code:
module ActiveSupport
# ...
module Concern
def append_features(base)
if base.instance_variable_defined?(:#_dependencies)
# ...
else
# ...
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) # <-- Notice this line
# ...
end
end
end
end
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'm missing something stupid. Help?
lib/api.rb
require 'httparty'
module API
def self.call_api(query)
base_url = "http://myapi.com"
return HTTParty.get("#{base_url}/#{query}.json")
end
end
models/job.rb
require 'api'
class Job
include API
def self.all(page=1)
self.call_api "jobs?page=#{page}"
end
end
Job::all
NoMethodError: undefined method `call_api' for Job:Class
If I move my "call_api" directly into the Job class, it works. What am I missing?
The other answers have pointed out your issue you should have extend instead of include:
class Job
extend API
You might also like to consider using the following pattern which aside from being useful can also help to alleviate the confusion, since you can tell straight away that your class methods are in the ClassMethods module while your instance methods are directly in the MyModule module.
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
end
end
def my_instance_method
end
end
This uses Ruby's included callback (which is fired every time a module in included in a class), we redefine the callback (which normally doesn't do anything), to extend the class in which the module was included with the ClassMethods sub module. This is a very common metaprogramming pattern in Ruby. If you use this pattern you no longer have to worry about extend, just use include:
class MyClass
include MyModule
then:
MyClass.my_class_method
MyClass.new.my_instance_method
You can also take this pattern a little bit further:
module MyModule
include ClassMethods
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
end
end
def my_instance_method
end
end
Notice that the parent module includes its child ClassMethods module directly. This way my_class_method becomes both an instance and a class method:
class MyClass
include MyModule
MyClass.my_class_method
MyClass.new.my_class_method
You have to be a bit careful if you do that about how you code your method in the ClassMethods child module. But I have found this pattern extremely handy on occasion.
I believe what you need is to define call_api as an instance method in your module, then extend API instead of include API. In general, include is for instance methods and extend is for class methods.
The problem is that you are defining the method call_api directly on the API module, rather than on the inheritance chain of the Job class.
You can call API.call_api just fine, but the problem is that API's "instance methods" are not part of the inheritance chain (not instance methods defined for the receiver of the included module, but API's singleton instance methods - remember API is an instance of the class Module with its own methods)
This is way of doing what you are trying to do:
module API
def self.included(base)
def base.call_api(...)
...
end
end
end
but there may be cleaner ways using extend rather than include on your classes.
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.