My module:
# test.rb
module Database
# not used in this example, but illustrates how I intend to include the module in class
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
attr_accessor :db, :dbname
self.dbname = ENV['RACK_ENV'] == 'test' ? 'mydb_test' : 'mydb'
end
end
When I load it, I get this:
test.rb:7:in `<module:ClassMethods>': undefined method `dbname=' for Database::ClassMethods:Module (NoMethodError)
from bin/test:7:in `<module:Database>'
from bin/test:1:in `<main>'
Can't figure out why. If I check instance_methods, it's empty before attr_accessor, and has the appropriate four methods after. Yet when I call them, they don't exist.
attr_accessor defines instance methods on ClassMethods (such as ClassMethods#dbname=), but you are trying to call a class method (ClassMethods.dbname=).
With self.dbname = ENV..., you are calling a method #self.dbname=, which doesn't exist. Maybe this is what you are after?
# test.rb
module Database
# not used in this example, but illustrates how I intend to include the module in class
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
attr_accessor :db, :dbname
def self.dbname ; ENV['RACK_ENV'] == 'test' ? 'mydb_test' : 'mydb' ; end
puts self.dbname
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
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
Ruby library code
module Yaffle
module ActsAsYaffle
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
include Yaffle::ActsAsYaffle::LocalInstanceMethods
end
end
...
more code
...
end
end
ActiveRecord::Base.send(:include, ActiveRecord::Acts::Taggable)
Why does when i call the acts_as_yaffle in the model, its being used like
class Hickwall < ActiveRecord::Base
acts_as_yaffle
end
Does the ActiveRecord::Base.send(:include,...) included the ClassMethods as instance eventhough the base is extended (base.extend(ClassMethods) ?
The acts_as_yaffle was declared as a ClassMethods.
It is a class method. Consider this example:
class C
def my_instance_method
puts 'hello from instance'
end
def self.my_class_method
puts 'hello from class'
end
my_instance_method
# NoMethodError
my_class_method
# hello from class
end
inside class C ... end you can only call class methods, because there is no instance to send the method call to. The library code is written using a common pattern to put class methods inside a module. For better understanding, the example class I wrote above is equivalent to this:
module M
def my_instance_method
puts 'hello from instance'
end
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
puts 'hello from class'
end
end
end
I am trying to understand why my class method was not recognized. Below is my code:
wiki_patch.rb
require_dependency 'wiki_content'
module WikiRecipientPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :recipients, :send_wiki_mail
end
end
end
module InstanceMethods
def self.set_mail_checker(mail)
#mail_checker = mail
end
end
Rails.configuration.to_prepare do
WikiContent.send(:include, WikiRecipientPatch)
end
controller.rb
WikiContent.set_mail_checker(params[:mail_checker_wiki])
I am getting this error:
NoMethodError (undefined method `set_mail_checker' for #<Class:0x4876560>):
Any idea why it happens and what the solution for it is?
You got the idiom slightly wrong.
ClassMethods/InstanceMethods modules are supposed to be nested in the "main" module (WikiRecipientPatch in this case).
You are including instance methods, but expecting class methods to somehow arise from this? Surely you meant extend ClassMethods, didn't you?
module WikiRecipientPatch
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def set_mail_checker(mail)
'mail checker'
end
end
end
class WikiContent; end
WikiContent.send(:include, WikiRecipientPatch)
WikiContent.set_mail_checker('whatever') # => "mail checker"
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.