Rails 3.1 Engines: Part I – The engine

Rails 3.1 Engines: Part I – The engine

During the last week I did a lot of research on how to extract some functionality from our Rails project into its own engine (and gem later on); this was necessary as our Rails project is becoming bigger and more complex every day. There’s a lot of information about engines on the net, but it’s quite hard for a beginner to distinguish current information from deprecated one, and often you have to pull your knowledge together from different sources.

So with this post I want to give people that are new to developing engines some useful, round out advice on how to do it. :-)

You can find the whole example engine code on my GitHub account.

Why Engines?

Engines can be described as something like small Rails apps that provide some functionality (for example authorization like login/logout, Devise is a good example) which can be mixed right into any other Rails app.

This is very useful as you can develop parts of your application on their own (and so test them in their own isolated environment without having to fear side effects of other components of your app) and you can reuse them in as many apps as you wish.

And since Rails 3.1 integrated the engines feature into its core functionalities, there’s nothing stopping you from developing engines whenever appropriate right away! Say goodbye to having big, confusing Rails apps and start developing small parts of reusable functionality today! :-)

Full vs. Mountable Engines

There are two kinds of engines:

  • “Full” engines are meant to be mixed right into your host app (the main Rails app, which includes engines, is called “host” app). So if e.g. you generate a controller in your engine, it will be available in the host app as if it were generated within the host! The same applies for models, views, helpers… even routes! And the nice thing is: you can easily overwrite an engine’s functionality easily within the host app, so if there’s a User model within the engine and the host app, the host’s one will be taken. This allows you to easily adapt any engine to your host app’s needs.
  • “Mountable” engines are meant to be separate parts of your host app, which means that typically they provide some self-standing functionality which doesn’t interfere with your host app. This could typically be a forum engine which adds functionalities like posting topics, comments, etc. to your host app (Forem is a good example). “Mountable” means that the whole engine will be mounted in its own namespaced route, e.g. www.example.com/my_engine/topics/123, so all your engine’s functionality is available there, while your host app’s functionalities are still available outside this namespace, e.g. www.example.com/users/123.

It’s up to you which kind of engine you use – it’s even possible to use both kinds together (I didn’t try this myself yet, though). Anyway, it’s only an argument that you pass to the rails plugin new command (which creates a skeleton for your new engine which you can then enhance the way you need), so here’s how you create an engine (be sure you are using at least Rails 3.1):

$ rails plugin new my_engine --full

Or:

$ rails plugin new my_engine --mountable

By the way: don’t ask me why the command is called “plugin” and not “engine” – my guess is that the term engine should be replaced with plugin in future versions of Rails.

Our own SimpleViewHelpers --full engine

Notice: I was inspired to this blog post mainly by the very good chapter 17 “Engines” of the book Rails 3 in Action by Ryan Bigg and Yehuda Katz where they describe how to create a --mountable engine. If you want to know more about the difference of full and mountable engines, go and take a look at this excellent book, as I will mainly focus on full engines in this blog post..

Let’s get our hands dirty! We want to create a small engine which provides some simple view helpers that can be used within our host application’s controllers and views, so we want to mix the engine’s functionality right into our host – which means we want to create a full engine!

$ cd ~/Desktop
$ mkdir my_first_engine
$ cd my_first_engine
$ rails plugin new simple_view_helpers --full --skip-test-unit --dummy-path=spec/dummy
$ cd simple_view_helpers

This will create the engine skeleton and run the bundle install command which makes sure that you have all needed gems installed. We also want to skip integration of test-unit since we will use RSpec for testing, so specify --skip-test-unit --dummy-path=spec/dummy.

As we want to deploy our gem to GitHub later, we init a git repository right away:

$ git init
$ git add .
$ git commit -m "Initial base"

Gems needed by your engine

You can specify gems that you need in your engine in its simple_view_helpers.gemspec file like this:

s.add_dependency "rails"
s.add_development_dependency "rspec-rails"
s.add_development_dependency "capybara"
s.add_development_dependency "guard-rspec"
s.add_development_dependency "guard-spork"
s.add_development_dependency "sqlite3"

add_dependency specifies gems that your engine needs when its used within the host app, while add_development_dependency specifies gems that you need while developing your engine (e.g RSpec for testing it, we’ll see how to do this later).

Run bundle make sure also the freshly added gems are available:

$ bundle

Optional: Use e specific sem set for your engine

I highly recommend you to use a specific gem set for your engine. You need to have RVM installed for this. I won’t go into details about this here, but the following command should be more or less what you need to execute on your system (in our case we want to use Ruby 1.9.3).

$ rvm --rvmrc --create 1.9.3@simple_view_helpers
$ cd ../simple_view_helpers/

This should prompt you whether you want to use the newly created .rvmrc file. Type y and press Enter. Now you have your completely isolated Ruby environment (execute a gem list command to verify this). To install all needed gems into your new environment, execute bundle again (this may take some minutes):

$ bundle

You should also add the .rvmrc file to git:

$ git add .
$ git commit -m "Added .rvmrc"

Tests! Tests! Tests!

At work, we are creating a Rails app that underlies quite a dynamic prototyping process where it isn’t always appropriate to write a lot of tests already, as many things are subject to change for a long time until we have evaluated how we really want to build them. As soon as we know this, we begin to extract the functionality into its own engine, where we can decorate it with tests. Behavior Driven Development is a shiny term, but it’s not always how things work in the real world. ;-)

But as we are creating our engine’s functionality from scratch, we can write tests for it immediately, so we should set up a good testing environment.

RSpec

While Rails still uses test-unit (and maybe always will, see this article), a big part of the Rails community is using RSpec, so let’s set up our test environment to use it.

Most of the following steps are take from this article, I won’t explain them any further here.

$ cd spec/dummy
$ ln -s ../../spec
$ rails generate rspec:install

Next we have to tell Rails to use RSpec as integration and testing framework when using the Rails generators (rails g). We do this by replacing the content of lib/simple_view_helpers/engine.rb with:

module SimpleViewHelpers
  class Engine < ::Rails::Engine
    config.generators.integration_tool :rspec
    config.generators.test_framework :rspec
  end
end

Nice! Add to git and commit:

$ cd -
$ git add .
$ git commit -m "Remove Test::Unit, replace with RSpec + Capybara"

Adding Spork and Guard

Spork and Guard are fantastic tools to develop in a TDD or even BDD way, and thankfully it’s pretty easy to set them up.

Spork

$ spork --bootstrap

Now replace the whole content of spec/spec_helper.rb with the following:

require 'rubygems'
require 'spork'

Spork.prefork do
  ENV["RAILS_ENV"] ||= 'test'

  require File.expand_path("../dummy/config/environment", __FILE__)
  require 'rspec/rails'
  require 'rspec/autorun'

  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  RSpec.configure do |config|
    config.use_transactional_fixtures = true
    config.fixture_path = "#{::Rails.root}/spec/fixtures"
    config.infer_base_class_for_anonymous_controllers = false
    config.treat_symbols_as_metadata_keys_with_true_values = true
    config.filter_run focus: true
    config.run_all_when_everything_filtered = true
  end
end

Spork.each_run do
  # This code will be run each time you run your specs.
end

Commit to git:

$ git add .
$ git commit -m "Added Spork"

Guard

$ bundle exec guard init spork
$ bundle exec guard init rspec

This created and initialized a Guardfile, wherein you should replace the whole…

guard 'rspec', version: 2 do
  # ...
end

…block with…

guard 'rspec', cli: "--color --drb", version: 2 do
  watch(%r{^spec/.+_spec\.rb$})
  watch(%r{^lib/(.+)\.rb$})    { |m| "spec/lib/#{m[1]}_spec.rb" }
  watch('spec/spec_helper.rb') { "spec" }

  # Rails example
  watch(%r{^spec/dummy/app/(.+)\.rb$})                          { |m| "spec/#{m[1]}_spec.rb" }
  watch(%r{^spec/dummy/app/(.*)(\.erb|\.haml)$})                { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
  watch(%r{^spec/dummy/app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
  watch(%r{^spec/support/(.+)\.rb$})                            { "spec" }
  watch('spec/dummy/config/routes.rb')                          { "spec/routing" }
  watch('spec/dummy/app/controllers/application_controller.rb') { "spec/controllers" }

  # Capybara request specs
  watch(%r{^spec/dummy/app/views/(.+)/.*\.(erb|haml)$})         { |m| "spec/requests/#{m[1]}_spec.rb" }

  # Turnip features and steps
  watch(%r{^spec/acceptance/(.+)\.feature$})
  watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$})             { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
end

Commit to git:

$ git add .
$ git commit -m "Added Guard"

This was a lot of work so far! Enough preparation, let’s dive into our engine! :-)

An engine’s folder structure looks much like…

…a “real” Rails application! Especially the app folder contains everything you know from Rails:

  • assets
  • controllers
  • helpers
  • mailers
  • models
  • views

You can use the app folder exactly the same way you know it: specify models, controllers, helpers there! Add views! Assets (images, javascripts, CSS)! They will all be mixed automatically right into your host app when you load the engine (except assets, which the host app needs to be told about; we’ll see how this happens later).

Having said that: where is our host app? We haven’t created it yet, as we don’t need it for developing our engine! Why’s that?

The dummy app

Take a look at the test/dummy folder: while the app folder we talked about a minute ago just sort of looks like a Rails app, the test/dummy folder is a Rails app! More specific: it is a completely working Rails app that automatically loads the engine we are working on. So we can develop our engine and test it right away using our dummy app. And this is very useful, because we can serve the dummy app using rails s (be sure you enter the command from within the test/dummy folder) to localhost:3000, and we can write integration tests that use the dummy app as the test environment.

The dummy app’s Gemfile

The dummy app is a complete Rails app itself, so it has its own Gemfile. You don’t have to put anything in there though as it references the simple_view_helpers.gemspec file using the gemspec command.

Small update: it seems that when you want to use some gem in your dummy app, you do have to specify it in the Gemfile! For example, if you’d like to use Slim instead of ERB in your views, you have to put gem 'slim' into the Gemfile! 

Our first helper

We want to create a small helper that accepts one argument and a block. It will output a <fieldset> element containing a <legend> tag (which will have the provided argument as the value) and the provided block. Invoke the following command in the root path of your engine (not in spec/dummy!):

$ rails g helper fieldset

Open app/helpers/fieldset_helper.rb in your text editor and add the following to the FieldsetHelper module:

def fieldset(legend, &block)
  render partial: 'simple_view_helpers/fieldset', locals: {
    legend: legend,
    block: capture(&block),
  }
end

We also need a view partial, so let’s create it…

$ mkdir app/views/simple_view_helpers
$ touch app/views/simple_view_helpers/_fieldset.html.erb

…and add the following content:

<fieldset>
  <legend><%= legend %></legend>
  <%= block %>
</fieldset>

Commit it to git:

$ git add .
$ git commit -m "Added fieldset helper"

Making use of the dummy app

What did we say about tests earlier? We should add them straight ahead. But it doesn’t make sense to write tests for something we don’t know if it’s really doing what we want, so sometimes Behavior Driven Development is not the right way to go. In our case, we want to check first, if our helper produces what we are expecting to see, and if so, we can add tests with confidence afterwards. But to see the helper in action, we have to call it somewhere within a view, and this is where the dummy app steps into the scene. So let’s create a controller with an action and corresponding view that makes use of our fieldset helper now!

Where do I have to put which functionality?

When developing an engine, it’s important to keep in mind where to put which functionality. In our case we need a controller and a view to checkout the result of our fieldset helper. This means that we only need a controller for testing purposes, it doesn’t provide any “real” functionality to the engine. And this means we should put it into the dummy application’s controllers folder (spec/dummy/app/controllers), not into the engine one’s (app/controllers)!

This can be a little confusing in the beginning, and as soon as you are also having a host app you are developing in parallel to your engine, you will have three controllers folders where you can pack functionality! ;-)

Adding a controller and view to the dummy app

Let’s first cd into the dummy app, so we can use the Rails generators from there:

$ cd spec/dummy

Then we can create a controller GeneralController with an action fieldset:

$ rails g controller general fieldset

I like to call this controller GeneralController because it serves a general function, and other probably fitting names like TestController, DummyController or even SimpleViewHelpersController wouldn’t be as easily distinguishable from all the other files and folders within this engine.

Let’s start the server!

The Rails generator has put everything into place! Let’s check it out in our browser. As already pointed out earlier, we can start our Rails server for our engine’s dummy app the same way we can do it for every other Rails app we developed already:

$ rails s

Just navigate with your browser to localhost:3000/general/fieldset and you will see something like “General#fieldset – Find me in app/views/general/fieldset.html.erb”.

Everything’s getting reloaded

Now what’s special is the fact that you can leave the dummy app’s server running and it reflects not only the changes you do to the dummy app itself, but also the changes you do to the engine’s files! Let’s first add the following line to app/views/general/fieldset.html.erb:

<%= fieldset 'This is a nice fieldset...' do %>
  ...with a pretty little block in it! :-)
<% end %>

Reload the server, and it will display this nice <fieldset> tag with its content, as desired. Now let’s change the helper a bit, so that the legend parameter is optional:

module FieldsetHelper
  def fieldset(legend = nil, &block)
    render partial: 'simple_view_helpers/fieldset', locals: {
      legend: legend,
      block: capture(&block),
    }
  end
end

And let’s change the app/views/simple_view_helpers/_fieldset.html.erb accordingly:

<fieldset>
  <% if legend %>
    <legend><%= legend %></legend>
  <% end %>
  <%= block %>
</fieldset>

Last, remove the caption argument from the fieldset call in the app/views/general/fieldset.html.erb view:

<h1>General#fieldset</h1>
<p>Find me in app/views/general/fieldset.html.erb</p>
<%= fieldset do %>
  ...with a pretty little block in it! :-)
<% end %>

Reload your browser – as you can see, it successfully reloaded all the changes, whether they are in the engine or in the dummy app. Hooraaayyy!! :-)

Finally: Tests!

This works as we expected, so let’s make sure that future code refactorings don’t break our nice fieldset helper.

First, start Guard from your engine’s root:

$ cd ../..
$ guard -c

The -c option tells Guard to always clear the screen when it automatically runs tests. This makes it easier to spot what’s happening.

You should see something like

Finished in 0.00413 seconds
1 example, 0 failures, 1 pending
Done.

(You can ignore the warning about “Missing dependency ‘rb-fsevent’” for the time being, we will get rid of it in the 2nd part of this series.)

Let’s add some tests!

Helper specs

Replace the content of spec/helpers/fieldser_herlper_spec.rb with:

require 'spec_helper'

describe FieldsetHelper do
  describe "#fieldset" do
    context "with caption" do
      let(:html) { helper.fieldset('This is the caption') { 'This is the <em>block</em>!'.html_safe } }
      subject { html }

      it "should generate a fieldset with a legend" do
        should have_selector('fieldset > legend')
        should have_selector('fieldset > em')
        should have_content('This is the block!')
      end
    end

    context "without caption" do
      let(:html) { helper.fieldset { 'This is the <em>block</em>!'.html_safe } }
      subject { html }

      it "should generate a fieldset without a legend" do
        should_not have_selector('fieldset > legend')
        should     have_selector('fieldset > em')
        should     have_content('This is the block!')
      end
    end
  end
end

You may have noticed: as soon as you save a file, Guard automatically runs the tests! Nice! :-)

View specs

Create a spec file for the view test:

$ mkdir -p spec/views/general
$ touch spec/views/general/fieldset.html.erb_spec.rb

Add the following content:

require 'spec_helper'

describe 'general/fieldset.html.erb' do
  before do
    render template: "general/fieldset"
  end

  subject { rendered }

  it 'should display the title' do
    should have_selector('h1')
    should have_content('General#fieldset')
  end

  it 'should display the paragraph' do
    should have_selector('p')
    should have_content('Find me in app/views/general/fieldset.html.erb')
  end

  it 'should display the fieldset without a legend' do
    should     have_selector('fieldset')
    should_not have_selector('fieldset > legend')
    should     have_content('...with a pretty little block in it! :-)')
  end
end

Integration specs

For the sake of completeness, let’s also have an integration test.

$ mkdir -p spec/integration/general
$ touch spec/integration/general/fieldset_spec.rb

Add the following content:

require 'spec_helper'

describe "general" do
  describe "fieldset" do
    before do
      visit '/general/fieldset'
    end

    subject { page }

    it "should load successfully" do
      should have_selector('h1')
      should have_content('General#fieldset')
      # You can do some crazy stuff here, like click_link('My link')...
    end
  end
end

That’s it! All your tests should be green (hit Enter in Guard’s console to re-run all tests manually):

Finished in 0.18793 seconds
6 examples, 0 failures
Done.

Time for a last commit to git:

$ git add .
$ git commit -m "Added tests"

What’s next?

An engine is only cool if it can be included into the host app by specifying it in its Gemfile! This is the next thing we will learn… check out Rails 3.1 Engines: Part II – The gem!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>