I am using Rails with MiniTest and have several classes that are all related through inheritance. I would like to reuse tests by placing them in a module. Something like this:
module MyModule
should 'work' do
assert true
end
end
Then in my tests:
class MyTest < ActiveSupport::TestCase
require MyModule
end
The problem is that I get a NoMethodError: undefined method should
What am I missing?
1) That's not what require is for.
2a) You probably want to extend a module and define a method that you can call instead.
module MyModule
def should_work
should 'work' do
assert true
end
end
end
class MyTest < ActiveSupport::TestCase
extend MyModule
should_work
end
2b) Alternatively, you can just extend the module and have the tests automatically called via Module.extended:
module MyModule
def self.extended(base)
base.should_work
end
def should_work
should 'work' do
assert true
end
end
end
class MyTest < ActiveSupport::TestCase
extend MyModule
end
2c) You can put everything in Module.extended too:
module MyModule
def self.extended(base)
base.should 'work' do
assert true
end
end
end
class MyTest < ActiveSupport::TestCase
extend MyModule
end
You need to require your module file in MyTest class, on the top:
require 'spec_helper'
require_relative '../spec/modules/module_file_name'
Or you can put this relative_path into spec_helper and than just require 'spec_helper' .
Hope this help you
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
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
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
I have the following example
class Test
configure_helper
end
module ConfigureHelper
module ClassMethods
def configure_helper
end
end
end
ConfigureHelper has some more functionality which will extend the class with ClassMethods in which the module was included.
So the problem is, if i use the following code to include the module
Test.send :include, ConfigureHelper
The Test class will be loaded and will raise a NoMethodError for configure_helper.
Is there any way to attach the configure_helper method so that configure_helper wont be called?
Why not include the module right in the class definition?
module ConfigureHelper
def self.included base
base.extend ClassMethods
end
module ClassMethods
def configure_helper
end
end
end
class Test
include ConfigureHelper
configure_helper
end
Try it
class Test
end
module ConfigureHelper
module ClassMethods
def self.included(base)
base.class_eval do
def configure_helper
p 'Yes'
end
end
end
end
end
Test.send(:include, ConfigureHelper::ClassMethods)
Test.new.configure_helper
I'm trying to refactor a superfat model which has quite a few lines of ActsAsStateMachine code related to the states and transitions, and I was hoping to refactor this to a module call CallStates.
#in lib/CallStates.rb
module CallStates
module ClassMethods
aasm_column :status
aasm_state :state1
aasm_state :state2
aasm_state :state3
end
def self.included(base)
base.send(:include, AASM)
base.extend(ClassMethods)
end
end
And then in the model
include CallStates
My question concerns how to include Module behavior into a Module such that a single Module can be included into the model. I've tried class_eval do as well to no avail. Thanks for any insightful thoughts you have on the matter.
You can't include one module in another exactly, but you can tell a module to include other modules in the class into which it's included:
module Bmodule
def greet
puts 'hello world'
end
end
module Amodule
def self.included klass
klass.class_eval do
include Bmodule
end
end
end
class MyClass
include Amodule
end
MyClass.new.greet # => hello world
It's best to only do this if Bmodule is actually data that is necessary for Amodule to function, otherwise it can lead to confusion because it's not explicitly included in MyClass.
You include a module into another module by ... including a module into another module, of course!
module Bmodule
def greet
puts 'hello world'
end
end
module Amodule
include Bmodule
end
class MyClass
include Amodule
end
MyClass.new.greet # => hello world
Rails
For Rails, you'll want to do something like this:
module_b.rb
module ModuleB
extend ActiveSupport::Concern
included do
include ModuleA
end
end
my_model.rb
class MyModel < ActiveRecord::Base
include ModuleB
end
ModuleA is being included in ModuleB, which is then included in MyModel class.
I like this syntax the best:
module Bmodule
def greet
puts 'hello world'
end
end
module Amodule
def self.included(receiver)
receiver.send :include, Bmodule
end
end
class MyClass
include Amodule
end
MyClass.new.greet # => hello world