Engines are a new feature within Rails 3 which allows for code such as that found in models and controllers to be shared in gems across applications. You may be familiar with the Devise gem, which is a very advanced engine. In this guide we cover how the foundation for the forem
engine was laid.
A lot of people have been saying there’s no real documentation or examples for Rails engines at the moment and that somebody should do something about it. Coincidentally, Rails 3 in Action will (eventually) have a chapter on creating an engine which will go into more detail than this guide, but this guide should help you get set up with an engine, as well as showing one setup of testing which uses RSpec + Capybara.
Hopefully this post can give you a nice kickstart on developing your own engine and you can learn from what we’ve gone through.
Generating an Engine
To begin with, I’m going to assume that you’ve got RVM installed and know how to use it. We’re going to be using it in this guide to separate any install we’ve got of Rails from the edge install that we’ll be doing. I’ll recommend here to use 1.9.2, but it’s not absolutely required for this guide. It’s just the latest + coolest.
Switch into a brand new gemset by running this command, which will also create an .rvmrc
file in our engine:
rvm use 1.9.2@rails-edge --create --rvmrc
Our first step will be installing Rails edge which contains an engine generator mainly by Piotr Sarnacki who has a guide of how to get started with engines too. We’re going to take a slightly different approach here. We can get edge rails by running this command:
git clone git://github.com/rails/rails
And then to install it we need to cd
into the directory and run rake install
:
cd rails rake install
When we do gem list
we should see that we’ve now got rails (3.1.0.beta)
along with the other Rails gems. We can also double check this by running rails -v
which will show us that we’ve got “Rails 3.1.0.beta” too.
Now with Rails installed, we can use it to generate the scaffold of our engine. We’re going to call it “forem”:
rails plugin new forem --mountable
The scaffold of this engine comes with the following helpful things:
* forem.gemspec: Each plugin that is generated using the new Rails plugin generator now comes with a gemspec which allows it to be used as a gem. Like in the gem development guide, this is where we need to specify the dependencies of our engine / gem so that when people install it the proper dependencies are installed.
* Gemfile: This file contains the rails
and sqlite3-ruby
gems as dependencies, but these should go into the gemspec
. Because we’re developing on an edge version of Rails, we may wish to change this line to point to the Git repository instead.
* app: Like a standard Rails application, an engine contains an app
folder. Inside this folder we’ve got the standard application controller, helper and layout, except these are all nested under the forem
namespace so that they’re only available to our engine.
* config/routes.rb: Again, like a standard Rails application, an engine has a config/routes.rb
file. This is where we define the routes for our engine. We can then mount our engine using mount Forem::Engine, :at => "forum"
in our application to host our engine at that path.
* lib/forem.rb: Defines the module Forem
which is where any helper code can go, such as configuration settings.
* lib/forem/engine.rb: Defines the Forem::Engine
class, which is the main “contact point” of applications and this engine.
* public/stylesheets and public/javascripts: Engines in Rails 3.1 can now have their own static assets, which is what these two directories provide. Note that this has given us the prototype files, but we can get it generate jQuery ones by passing the -j jquery
option to the generator.
* script/rails: Allows us to use the familiar rails server
, rails generate
and rails console
commands for our engine. Exceptionally handy.
* test: A relic from years gone by, we’ll replace this directory with a spec
one shortly.
* test/dummy: The most fun part of an engine. This is actually a mini-Rails app in your engine. When we run the tests later on, the suite will boot this application which includes our engine and then perform requests on it. We’ll move this directory to spec/dummy
after we install RSpec.
One more interesting thing here is that, because we passed --mountable
to our generator, in test/dummy/config/routes.rb
there’s this line:
mount Forem::Engine, :at => "/forem"
This is the line used to connect the engine’s routes to the application’s, and will prefix all of the engine’s routes with “/forem”. It’s not compulsory that this be placed at the “/forem” path, it could be anything. Any routes prefixed with this path will now be served by the engine rather than the application.
When developing this engine, we’re probably going to want to make sure that it’s working as we want it to. We could use the test
directory that comes with engines, but this is not 2001. We’re going to use RSpec instead. (Oh, and RSpec’s the preferred testing framework in the Rails community)
Installing RSpec
First, we need to inform our engine of a new dependency for development and we can do this by opening up the forem.gemspec
file and just before the end
of the Gem::Specification.new
block we can put this line:
s.add_development_dependency "rspec-rails", "~> 2.5"
The Gemfile
has the gemspec
method called in it, which means that we can run bundle install
and Bundler will read the dependencies of our gemspec as well as those specified in the Gemfile
. Let’s run this command now to install this version of RSpec.
When we run rails g rspec:install
in a Rails application, it creates a spec/spec_helper.rb
file for us. If we ran it inside an engine, it would create the same spec/spec_helper.rb
file as what’s needed in an application. This isn’t quite what we need. Our spec/spec_helper.rb
needs to load the dummy application’s config/environment.rb
file rather than one that is supposed to be a couple of levels up, for starters.
The first thing we’ll do here is move the test/dummy
application into a new folder of spec/dummy
and remove the test
directory altogether. After this we’ll need to update the location in the Rakefile
as well as script/rails
to now point to spec/dummy
instead of test/dummy
.
Next, we’ll create the spec/spec_helper.rb
file and fill it with this content:
# Configure Rails Envinronment ENV["RAILS_ENV"] = "test" require File.expand_path("../dummy/config/environment.rb", __FILE__) require 'rspec/rails' ENGINE_RAILS_ROOT=File.join(File.dirname(__FILE__), '../') # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[File.join(ENGINE_RAILS_ROOT, "spec/support/**/*.rb")].each {|f| require f } RSpec.configure do |config| config.use_transactional_fixtures = true end
At the top of this file we set the Rails environment to test and then require the dummy application’s config/environment.rb
file. This initializes the Dummy application so that we can then perform tests on it as we please.
Next, we set an ENGINE_RAILS_ROOT
variable that points at the root of our engine. This is done so that we can access it in our tests or RSpec configuration, which we do on the next line of code. The line of code beginning with Dir[
requires all the files in the spec/support
directory and all it’s sub folders. These files should set up extra configuration for RSpec testing (such as routes.rb
which includes the engines routes, oops spoilers). It’s just a good way of separating out configuration into easy-to-manage chunks.
With our spec
directory set up we can go ahead and create spec/controllers
and spec/models
directory and put our tests in there just like we would an application. Remember though, these files should be under the namespace of the engine so that they don’t conflict with any files in the main application!
While controller and model tests are fun, full-on integration tests is a better place to start. We can do this using RSpec + Capybara. It can also be done using Cucumber, but I won’t cover it in this post.
Installing & Using Capybara
Capybara’s a browser simulator which we can use for testing our engine. It supports a number of drivers such as Rack::Test
and Selenium and has a kick-ass API.
To get started with Capybara we’re going to need to add this as another development dependency to our forem.gemspec
, which we can do just under the rspec
line like this:
s.add_development_dependency "capybara", "~> 0.4"
When we run bundle install
again, this time it’ll install the latest 0.4 version of Capybara (as of this writing, that’s 0.4.1.2).
In this situation, we can use Capybara for integration testing that goes a little like this:
require 'spec_helper' describe "forums" do before do @forum = Forem::Forum.create!(:title => "Welcome to Forem!", :description => "A placeholder forum.") end it "listing all" do visit forums_path page.should have_content("Welcome to Forem!") page.should have_content("A placeholder forum.") end it "visiting one" do visit forum_path(@forum.id) within("#forum h2") do page.should have_content("Welcome to Forem!") end end end
Some people may prefer this syntax as all the code is there in Ruby, in the test. Personally, I can do whatever people on my team do.
This code goes in the spec/integration/forums_spec.rb
file and uses a couple of methods, such as visit
and within
from Capybara. So to get this to work we need to include Capybara
within our RSpec configuration, which we can do in spec/spec_helper
’s RSpec.configure
block, changing it to this:
RSpec.configure do |config| config.use_transactional_fixtures = true config.include Capybara, :example_group => { :file_path => /\bspec\/integration\// } end
By including Capybara
like this, we make the methods from this module available in all specs in the spec/integration
directory, but no other.
In the above example we make reference to the forums_path
and forum_path
routing helpers, which aren’t yet defined. Routes for an engine are defined in config/routes.rb
, just like in a normal Rails application. We can define the routing helpers for our test by making our engine’s config/routes.rb
this:
Forem::Engine.routes.draw do resources :forums end
Now, these routing helpers aren’t available immediately even still. At the moment, because of how our application is loaded, we’ve only got access to the routing helpers from the dummy application. To gain access to the engine’s routes we must add this line to RSpec.configure
in spec/spec_helper.rb
:
config.include Forem::Engine.routes.url_helpers
Now we’ll be able to run our test with our routes, and so we’ve got a great place to begin developing our application using RSpec + Capybara.
Release the hounds engine
To release this engine, the best way would be to make it into a gem. Luckily, we’ve already got the needed gemspec
file for this, but we’re lacking the Rake task to put it on Rubygems. We can add these Rake tasks to our project by adding this line to our Rakefile
:
Bundler::GemHelper.install_tasks
This will give us the necessary rake build
and rake release
commands we need to build and release the engine out to the world.
One more thing we need to configure is the files that will be included with our engine when it is packaged as a gem. Currently in forem.gemspec
there’s this line:
s.files = Dir["lib/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
This will only include files from the lib
directory and the MIT-LICENSE
, Rakefile
and README.rdoc
files. We’d like it to contain everything, and so we will replace that line with this one:
s.files = `git ls-files`.split("\n")
And now we’ll be able to push this gem out to the world and people will be able to use it within their applications.
As an example of an engine that does this already, check out the forem engine project, which aims to be a basic forum system (one day!) and was the basis for this post.