I'm writing an acts_as_thingy module, intended to be used as per
class TestThingy
include ActsAsThingy
acts_as_thingy :name
end
ActsAsThingy is defined as
module ActsAsThingy
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_thingy *attributes
attributes.each do |attribute|
define_method attribute do
"thingy - #{attribute.to_s}"
end
end
end
end
end
And tested as
describe ActsAsReadOnlyI18nLocalised do
let(:thingy) { TestThingy.new }
it 'has a name method' do
expect(thingy.name).to eq "thingy - name"
end
end
What happens however is that, when I run the rspec, ActsAsThingy's self.included method is never invoked, and rspec complains that there is no such method as acts_as_thingy.
I seem to be missing something entirely obvious, but just can't see it.
Why isn't the self.included method being called when I include ActsAsThingy in the class?
update
Stepping through with pry I can see that after the include ActsAsThingy, if I then look at self.included_modules it shows up as [ActsAsThingy, PP::ObjectMixin, Kernel] So the include is working, it's not a paths issue or anything like that. The core question remains; why isn't self.included being invoked?
So after all that it turned out that I simply needed to add
require 'acts_as_thingy' to the top of the file that contained
class TestThingy
include ActsAsThingy
acts_as_thingy :name
end
I am not sure why Ruby didn't just throw an error when it couldn't find ActsAsThingy but it explains why the self.included method never got called (the include failed, but silently).
Check your scope.
Check your file require path.
Include is used for including methods into other
Modules
Require is what you want to use in your case.
Read up on the differences between require and include:
What is the difference between include and require in Ruby?
Here is my test code for your problem.
class A
require_relative 'test.rb'
end
p('test')
module Test
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def p(str)
print "#{str}"
end
end
end
output:
vagrant [test]> ruby file.rb
"test"
Related
I'm trying to use prepend method in ruby to overwrite methods of a class, here is how my code works:
module PrependedModule
def self.klass_method
puts 'PrependedModule klass_method'
end
def instance_method_a
puts 'PrepenedModule instance method'
end
end
class SomeClass
prepend PrependedModule
def self.klass_method
puts 'SomeClass klass_method'
end
def instance_method_a
puts 'SomeClass instance_method'
end
end
SomeClass.klass_method
SomeClass.new.instance_method_a
#output:
#SomeClass klass_method
#PrependedModule instance method
#expected:
#PrependedModule klass_method
#PrependedModule instance method
The output of this script is shown above, as we can see, the instance method instance_method_a was overwritten by module PrependedModule, but not the class method klass_method, when I called klass_method on SomeClass, it still executes it's origin method, instead of the one defined in PrependedModule.
I'm confused about this and don't know what happend with class methods when using prepend. It would be a great help if anyone can solve this question for me.
Singleton classes do not work that way. You need to explicitly prepend the methods to the eigenclass of SomeClass:
module PrependedModule
module ClassMethods
def klass_method
puts 'PrependedModule klass_method'
end
end
def instance_method_a
puts 'PrepenedModule instance method'
end
end
class SomeClass
# prepending to the class
prepend PrependedModule
class << self
# prepending to the eigenclass
prepend PrependedModule::ClassMethods
end
def self.klass_method
puts 'SomeClass klass_method'
end
def instance_method_a
puts 'SomeClass instance_method'
end
end
With include or prepend you can only gain access to a module's instance methods1. You therefore might ask if there is any reason to define module methods on a module. The answer is a resounding "yes". There are two reasons you might want to do that.
The first has nothing to do with including or prepending the module. You need only look at the module Math to understand why you might want to do that. All methods defined on Math are module methods. These constitute a library of useful functions. They are of course methods, but since all have Math as their receiver, they behave like functions in non-OOP languages.
The second reason is that you might want to define a callback method (aka hook method) on a module that is to be included or prepended by another module. The main ones are Module#included, Module#prepended, Module#extended, Class#inherited and BasicObject#method_missing. The last of these is an instance method; the others are module methods. An example of Module#prepended is given below.
#mudasoba has shown how to confine instance methods to a sub-module Sub of Mod so that Mod::Sub can be prepended (or included) to a class's (or module's) singleton class. A commonly-used pattern for doing that employs the callback Module#prepended. It follows.
module PrependedModule
module ClassMethods
def klass_method
puts 'PrependedModule klass_method'
end
end
def instance_method_a
puts 'PrepenedModule instance method'
end
def self.prepended(mod)
mod.singleton_class.prepend(ClassMethods)
end
end
class SomeClass
def self.klass_method
puts 'SomeClass klass_method'
end
def instance_method_a
puts 'SomeClass instance_method'
end
prepend PrependedModule
end
SomeClass.klass_method
# PrependedModule klass_method
SomeClass.new.instance_method_a
# PrepenedModule instance method
1 I've always found it curious that instance methods can be defined on modules (that are not classes), considering that such modules cannot have instances. True, these methods become instance methods in classes that include or prepend the module, but keep in mind that those modules can be included or prepended by other modules (that are not classes) as well. One might therefore expect such methods to have some name other than "instance method". Finding a suitable alternative would be a challenge, however, which is perhaps one reason why that nomenclature has persisted.
Someone can comment on when this was introduced, but my personal experience and also suggested by https://stackoverflow.com/users/256970/cary-swoveland in comments section of the selected answer, you can always do
class Source
def self.hello
puts "hello"
end
end
module Extension
def hello
# you're also allowed to call super from here
puts "world"
end
end
Source.singleton_class.prepend Extension
Now if you call Source.hello, the method from the Extension module will be called.
This is also valid for overriding Module's static/class methods But I've mentioned the code explicitly because I've rarely come accross this solution on S/O.
module Source
def self.hello
puts "hello"
end
end
module Extension
def hello
# you're also allowed to call super from here
puts "world"
end
end
Source.singleton_class.prepend Extension
class Foo
singleton_class.prepend ClassMethods
def self.hello
puts "hi"
end
end
module ClassMethods
def hello
puts "ho"
end
end
I have the following module and classes:
module MyModule
def self.included base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
# this method MUST be called by every class which includes MyModule
def configure &block
#config = {}
block.call(#config) if block
end
end
end
class A
include MyModule
configure do |config|
# do sth with the config
end
end
class B
include MyModule
end
Is it possible to check, if the configure method from the module was called? This means A should be fine, but B should throw an error, because it never called configure.
I tried it within the self.included callback, but the configure method gets called afterwards.
Technically, #ndn is right, it could be called after the class has been evaluated. However, it sounds like what you want is to validate that the configure method has been called at some point within the class body definition (this will also allow any modules that have been included, to finish evaluating, so if a module include calls the configure method, it's all good as well).
The closest solution I've come up to address this situation can be found here:
https://github.com/jasonayre/trax_core/blob/master/lib/trax/core/abstract_methods.rb
The above code is an abstract methods implementation for ruby, which technically isn't what you're asking (you are talking about calling the method, abstract methods are about checking that a subclass defined it), but the same trick I used there could be applied.
Basically, I'm using ruby's trace point library to watch for the end of the class definition to hit, at which point it fires an event, I check whether the method was defined, and throw an error if not. So as long as you're calling configure from WITHIN your classes, a similar solution could work for you. Something like (not tested):
module MustConfigure
extend ::ActiveSupport::Concern
module ClassMethods
def inherited(subklass)
super(subklass)
subklass.class_attribute :_configured_was_called
subklass._configured_was_called = false
trace = ::TracePoint.new(:end) do |tracepoint|
if tracepoint.self == subklass #modules also trace end we only care about the class end
trace.disable
raise NotImplementedError.new("Must call configure") unless subklass._configured_was_called
end
end
trace.enable
subklass
end
def configure(&block)
self._configured_was_called = true
#do your thing
end
end
end
class A
include MustConfigure
end
class B < A
configure do
#dowhatever
end
end
class C < B
#will blow up here
end
Or, you could try using the InheritanceHooks module from my library and skip the manual tracepoint handling:
class BaseClass
include::Trax::Core::InheritanceHooks
after_inherited do
raise NotImplementedError unless self._configure_was_called
end
end
Note, although I am using this pattern in production at the moment, and everything works great on MRI, because tracepoint is a library built for debugging, there are some limitations when using jruby. (right now it breaks unless you pass the jruby debug flag) -- I opened an issue awhile back trying to get tracepoint added in without having to enable debug explicitly.
https://github.com/jruby/jruby/issues/3096
Here's an example based on your structure.
It checks at instantiation if configure has been called, and will work automatically with any class on which you prepended MyModule.
It checks at every instantiation if configure has been called, but it is just checking a boolean so it shouldn't have any performance impact.
I looked for a way to undefine a prepended method for a specific class but didn't find anything.
module MyModule
def self.prepended base
base.extend(ClassMethods)
end
module ClassMethods
attr_reader :config
def configured?
#configured
end
def configure &block
#configured = true
#config = {}
block.call(#config) if block
end
end
def initialize(*p)
klass = self.class
if klass.configured? then
super
else
raise "Please run #{klass}.configure before calling #{klass}.new"
end
end
end
class A
prepend MyModule
configure do |config|
config[:a] = true
puts "A has been configured with #{config}"
end
end
class B
prepend MyModule
end
A.new
puts "A has been instantiated"
puts
B.new
puts "B has been instantiated"
# =>
# A has been configured with {:a=>true}
# A has been instantiated
# check_module_class.rb:27:in `initialize': Please run B.configure before calling B.new (RuntimeError)
# from check_module_class.rb:50:in `new'
# from check_module_class.rb:50:in `<main>'
This should be an easy one for a ruby dev. I'm playing around with a gem and I need help with inheriting module variables. Code should speak better than me:
module SomeModule
extend ActiveSupport::Concern
attr_accessor :bbonified
class Railtie < Rails::Railtie
initializer "some_module.study" do
Rails.application.eager_load!
# => I WANT TO ACCESS HERE
puts #bbonified
end
end
module ClassMethods
def bbonify(*columns)
# => WHAT I DEFINE HERE
#bbonified = columns
end
end
end
ActiveRecord::Base.send(:include, SomeModule)
You're not going to be able to access #bbonified directly, that's a local variable in the class that imports this module.
You need to define a separate accessor method to retrieve it:
module ClassMethods
def bbonified
#bbonified
end
end
Then later you need to refer to this somehow, but as you're just talking about a module it will depend on what class has been extended.
Rails.application.eager_load!
SomeClass.bbonified
I have the following module:
# lib/translator/sms_command.rb
module Translator
module SmsCommand
def self.included(klass)
puts "SmsCommand initialized in #{klass}"
end
def alias_command(name)
end
end
end
And the following spec:
# spec/translator/sms_command_spec.rb
require 'spec_helper'
class ExampleCommand
include Translator::SmsCommand
alias_command :x
end
module Translator
describe SmsCommand do
describe "#alias_command" do
it "registers the class with the command registry" do
Translator::SmsCommand.command_registry.should include_key :x
end
end
end
end
Yes #alias_command does nothing at this stage but that's because I'm in the midst of developing it. However, when I run my spec I am seeing...
SmsCommand initialized in ExampleCommand
so the module is certainly being mixed in, however the spec barfs on the alias_command :x line in the ExampleCommand as if the alias_command method is never becoming available.
/spec/translator/sms_command_spec.rb:5:in `<class:ExampleCommand>': undefined method `alias_command' for ExampleCommand:Class (NoMethodError)
I could solve this problem through inheritance though I'd prefer the mixin module. What am I missing?
It's because it's not an include but an extend to access alias_command in your Class method
class ExampleCommand
extend Translator::SmsCommand
alias_command :x
end
You want to define a class method called alias_command, don't you?
In this case you need to extend the class with the module, a simple include will turn the included method into an instance method!
You can do it in a widely accepted way as follows:
module Translator
module SmsCommand
def self.included(klass)
puts "SmsCommand initialized in #{klass}"
klass.extend ClassMethods
end
module ClassMethods
def alias_command(name)
end
end
end
end
This way when you include the module, it will automatically extend the target class with the class methods!
In a bunch of rspec rails unit specifications I do something like:
describe Foo do
[:bar, :baz].each do |a|
it "should have many #{a}" do
Foo.should have_many(a)
end
end
end
For cleaner code I'd rather do something like:
describe Foo do
spec_has_many Foo, :bar, :baz
end
So how do I write a helper method like spec_has_many() for inserting DSL code like rspec's it() method? If it were for an ordinary instance method I'd do something like:
def spec_has_many(model, *args)
args.each do |a|
define_method("it_should_have_many_#{a}") do
model.should have_many(a)
end
end
end
What would be the equivalent for defining rspec examples?
Ok, this took some messing around, but I think I got it working. It's a bit of metaprogramming hackery, and I personally would just use the first thing you described, but it's what you wanted :P
module ExampleMacros
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
# This will be available as a "Class Macro" in the included class
def should_have_many(*args)
args.each do |a|
# Runs the 'it' block in the context of the current instance
instance_eval do
# This is just normal RSpec code at this point
it "should have_many #{a.to_s}" do
subject.should have_many(a)
end
end
end
end
end
end
describe Foo do
# Include the module which will define the should_have_many method
# Can be done automatically in RSpec configuration (see below)
include ExampleMacros
# This may or may not be required, but the should_have_many method expects
# subject to be defined (which it is by default, but this just makes sure
# it's what we expect)
subject { Foo }
# And off we go. Note that you don't need to pass it a model
should_have_many :a, :b
end
My specs fail because Foo doesn't have a has_many? method, but both tests run, so it should work.
You can define (and rename) the ExampleMacros module in your spec_helper.rb file and it will be available for inclusion. You want to call include ExampleMacros in your describe blocks (and not any others).
To make all of your specs include the module automatically, configure RSpec like so:
# RSpec 2.0.0
RSpec.configure do |c|
c.include ExampleMacros
end