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 / 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]
(https://github.com/radar/forem/blob/3d71dc6e0b5603789a269cfa7db36f66379eb85f/spec/support/routes.rb){: target=”_blank”}, 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.
Summary
This article provides a detailed, step-by-step guide on how to generate and set up a basic Rails engine in Rails 3, focusing on testing with RSpec and Capybara. Here’s a breakdown of the key topics and insights:
Core Concepts:
-
Rails Engines: Introduced in Rails 3, engines allow developers to create reusable components with their own models, controllers, and views, which can be mounted into other Rails applications. This promotes code sharing and modularity.
-
Mountable Engines: The guide focuses on creating a mountable engine (rails plugin new
forem --mountable
), which means it has its own isolated routing and can be integrated into an application at a specific path (e.g.,/forem
). -
Gemspec: Each generated plugin (engine) includes a .gemspec file, crucial for packaging the engine as a gem and specifying its dependencies.
-
Dummy Application: A mini Rails application is generated within the engine’s test directory (later moved to spec/dummy). This dummy app is used to test the engine’s functionality in a realistic Rails environment.
Setting Up the Development Environment:
-
RVM (Ruby Version Manager): The guide recommends using RVM to isolate the Rails edge installation used for engine development.
-
Rails Edge: The article uses a development version of Rails 3.1 (beta at the time) to leverage the engine generation features.
-
Engine Generation: The command rails plugin new forem –mountable is used to create the basic structure of the forem engine.
Engine Structure Breakdown:
The article explains the purpose of key files and directories within the generated engine:
-
.gemspec: Specifies engine metadata and dependencies.
-
Gemfile: Initially contains Rails and SQLite3, but the guide suggests moving these to the .gemspec for gem compatibility.
-
app: Contains namespaced controllers, helpers, and layouts specific to the engine.
-
config/routes.rb: Defines the engine’s routes, which are mounted in the main application.
-
lib/forem.rb: Defines the main Forem module for helper code and configuration.
-
lib/forem/engine.rb: The entry point for the engine within Rails applications.
-
public/stylesheets and public/javascripts: For the engine’s static assets (new in Rails 3.1).
-
script/rails: Allows running Rails commands within the engine’s context.
-
test (later spec): Initially for testing, but the guide advocates for RSpec.
-
test/dummy (later spec/dummy): A minimal Rails application used for testing the engine’s integration.
Testing with RSpec and Capybara:
-
RSpec Installation: The guide details how to add rspec-rails as a development dependency in the .gemspec and install it using bundle install.
-
Spec Helper Configuration: It explains the need to create a custom spec/spec_helper.rb that loads the dummy application’s environment to properly test the engine within a Rails context.
-
Directory Restructuring: The test/dummy directory is moved to spec/dummy, and the test directory is removed. Corresponding updates are made in Rakefile and script/rails.
-
Integration Testing with Capybara: The article introduces Capybara as a browser simulator for integration testing. It demonstrates how to add capybara as a development dependency.
-
Writing Integration Specs: An example integration test (forums_spec.rb) using Capybara’s visit and within methods is provided to test basic forum listing and viewing.
-
Integrating Engine Routes in Tests: The guide shows how to define routes within the engine’s config/routes.rb and then include the engine’s route helpers in the spec_helper.rb to make them available in the tests.
Releasing the Engine as a Gem:
-
Bundler::GemHelper: The guide explains how to add Bundler::GemHelper.install_tasks to the Rakefile to enable Rake tasks for building and releasing the gem.
-
Specifying Included Files: It highlights the importance of configuring the s.files setting in the .gemspec to include all necessary files for the gem. The guide recommends using git ls-files to dynamically include all tracked files.
Key Takeaways and Insights:
-
Rails engines are a powerful mechanism for creating reusable and modular code in Rails applications.
-
Mountable engines provide a clean way to integrate isolated functionality.
-
Testing engines within a realistic Rails environment using a dummy application is crucial.
-
RSpec and Capybara are presented as preferred tools for testing Rails engines, offering different levels of testing (unit, integration).
-
Properly configuring the .gemspec and Rake tasks is essential for packaging and releasing the engine as a gem.
-
The article addresses the initial lack of comprehensive documentation for Rails engines by providing a practical, hands-on guide.
Conclusion
While the specific versions and some details in the provided text refer to the state of Rails around 2011-2012, the fundamental concepts and much of the advice regarding Rails engines remain highly relevant today. This is a valuable resource for developers looking to understand and build their own Rails engines, emphasising a robust testing strategy using modern Ruby testing tools.