After watching the Fasts Rails Tests talk by Corey and reading through Object on Rails by Avdi I am in the process of setting up my new Rails 3.2 application to take advantage of these concepts.
In order to get everything working I started with this example spec file.
# spec_no_rails/projects/financials_spec.rb
require_relative '../spec_no_rails_helper'
class DummyProject
include Modules::Projects::Financials
end
describe Modules::Projects::Financials do
it 'should have some method' do
DummyProject.new.foo.should == 'bar'
end
end
And this was the intialial spec_no_rails_helper.rb file that is used to require the modules
# spec_no_rails/spec_no_rails_helper.rb
Dir["#{Dir.pwd}/app/pimms/**/*.rb"].each { |file| require file }
I then set about creating the new example module.
# app/pimms/modules/projects/financials.rb
module Modules::Projects::Financials
def foo
'bar'
end
end
In order to see that everything was going to work when I included the new stand alone module into one of my ActiveRecord classes I added the following line into one of my models.
# app/models/project.rb
class Project < ActiveRecord::Base
include Modules::Projects::Financials
end
This allowed me to open up console and see that everything is working as expected.
> Project.first.foo
=> "bar"
So at this stage I have defined a namespaced stand alone module defined under app/pimms/modules/projects/financials.rb which I can included into a Rails model and everything works as expected.
The problem I’m having is when I try to run the specs I get the following.
> bundle exec rspec spec_no_rails/
/Users/scott/Code/pimms/spec_no_rails/projects/financials_spec.rb:5:in `<class:DummyProject>': uninitialized constant DummyProject::Modules (NameError)
from /Users/scott/Code/pimms/spec_no_rails/projects/financials_spec.rb:4:in `<top (required)>'
from /Users/scott/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `load'
from /Users/scott/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `block in load_spec_files'
from /Users/scott/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `map'
from /Users/scott/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `load_spec_files'
from /Users/scott/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.8.0/lib/rspec/core/command_line.rb:22:in `run'
from /Users/scott/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:80:in `run_in_process'
from /Users/scott/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:69:in `run'
from /Users/scott/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:10:in `block in autorun'
So the reason this happens is because the Modules::Projects namespaces has not been defined when the tests are being run. I’m guesing I didn’t have to define the Modules::Projects namespace when I used the module with the Rails application because Rails handled that for me.
In order to get the test to run as expected I had to define the namespace in the spec_no_rails_helper.rb file like this.
# spec_no_rails/spec_no_rails_helper.rb
module Modules
module Projects
end
end
Dir["#{Dir.pwd}/app/pimms/**/*.rb"].each { |file| require file }
This obviously isn’t ideal as I would have to manually create all the namespaces for any stand alone modules or classes that I wasnt to test outside of Rails.
Is there a better way to setup my Rails application so that I can easily run a test suite without relying on Rails?
I think this is because of how you’ve defined the module. You’re attempting to define it like this:
But Ruby will interpret this by attempting to find the
Modulesmodule first, then theProjectsmodule and then will finally define theFinancialsmodule, but ONLY if it can find the first two.In your case, it can’t find the first two and so bombs out like that.
Go ahead, fire up
irband copy and paste the above code example into it. You’ll see exactly the same error that you’re getting when you run your specs.The way to fix this is to just define/re-open the module in each file:
Now that each time each one of these files is loaded, it will either define or re-open the modules, adding functionality to them. The best part about this is that it doesn’t matter if
Modulesis or isn’t defined first, it’ll just define it anyway.Now to address the point: Why does it work in Rails?
Oh man, Rails is totally doing some awesome stuff!
I actually covered how Rails deals with the automatic definition of modules in my “wrong argument type” screencast. Well, I don’t exactly cover how modules are automatically loaded, but (spoiler alert) it’s the culprit for what’s going wrong there.
I’m not going to force you to watch it. The problem lies with these lines inside
activesupport/lib/active_support/dependencies.rb.The
load_missing_constantmethod is used by Rails when it can’t find a constant. That’s when Rails’s magic autoloading stuff kicks into gear. It calls this method, and attempts to find a file that defines this module.If it can’t do that, it does this:
This is inside the
autoload_module!method in thatdependencies.rbfile.What this code does is simple: creates a new module, sets its constant name to the one that’s missing, adds the
qualified_nameto theautoloaded_constantsand returns that module.This is why Rails is defining your modules even though they don’t really exist. You’re completely bypassing this in your spec (with good reason, you don’t want all of that nasty Rails junk), and so it’s not automatically loading the modules.