Ruby


13
Dec 10

Mountable engines in Rails 3.1 beta: getting started

Photo courtesy Tambako the Jaguar.

As Rails matures, it’s becoming more and more common to see it powering large-scale applications with many moving parts. These can be a chore to maintain in any framework, but a reusability mechanism can make all the difference in the world, if only so you can break code into mentally digestible areas.

Until recently, Rails lacked a recommended way to tackle this. Plugins didn’t do the job, and engines weren’t officially supported. Without that support, it was difficult to justify making them a fundamental dependency of your application. Of course, engines were the way to go in the end, and in Rails 3 they were finally merged into Rails itself.

There are still a few speed bumps for engines in the current version (at the time of writing, 3.0.3). Luckily, Piotr Sarnacki devoted his summer to improving engine support and has done a fantastic job bringing us closer still to true mountable applications. So let’s try it out!

By the end of this article, you should have a functioning application and engine running on edge Rails (3.1 beta). We’ll cover the other aspects of this in a future article, but this will get you up and running.

The road to bundle install

In this first section, we just want to do enough to get our first bundle install going.

Let’s create our main application and the engine.

rails new papa_bear
rails new baby_bear

Now we’ll need a gem specification. Bundler has a command for this, but first we’ll need to get rid of the Gemfile that Rails created. We’re also going to update Bundler to ensure that the bundle gem command works how we want it to—it fails with older versions.

rm baby_bear/Gemfile
sudo gem update bundler
yes | bundle gem baby_bear

The last command pipes bundle gem through yes, automatically overwriting the Rakefile and .gitignore files (we don’t need the original versions).

Now you need to declare a dependency on baby_bear from papa_bear. In papa_bear/Gemfile, add the following line:

gem 'baby_bear', :path => '../baby_bear'

By default, Rails assumes you want to depend on the version of Rails that you used to create the application. We want to use edge Rails, so we’ll have to change that.

Remove this line from papa_bear/Gemfile:

gem 'rails', '3.0.x'

Then replace it with these lines:

gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'arel',  :git => 'git://github.com/rails/arel.git'
gem 'rack',  :git => 'git://github.com/rack/rack.git'

(Edge Rails depends on edge Arel and edge Rack, also—release versions won’t cut it.)

For completeness, we should also be explicit that the baby_bear gem depends on Rails. For this, we can’t explicitly depend on Git (as we can in papa_bear’s Gemfile), but we should at least make the dependency meet 3.0. Since papa_bear’s Rails dependency satisfies baby_bear’s, this should be fine.

Add this to baby_bear.gemspec:

s.add_dependency 'rails', '>= 3.0.0'

Alright, we’re ready to go!

cd papa_bear
bundle install

And our dependencies are installed.

Keep in mind that when working with engines, you only ever need to run bundle install from the main application. It pulls in all the engines’ required gems.

Also, be sure not to run the last command as sudo.

Onto rails server

Because the engine isn’t a complete app, we can get rid of some configuration to reduce confusion.

cd ../baby_bear
rm config/application.rb
rm config/boot.rb
rm config/database.yml
rm config/environment.rb
rm -rf config/environments
rm -rf config/initializers

In papa_bear/config/routes.rb, add this line within the main block:

mount BabyBear::Engine => '/baby-bear'

There isn’t actually a BabyBear::Engine right now, but before we create it let’s make sure we remove any references to BabyBear::Application. If you’ve been following the instructions, this should only be in baby_bear/config/routes.rb. Replace BabyBear::Application.routes.draw with BabyBear::Engine.routes.draw.

Now the engine. In baby_bear/lib/baby_bear.rb, add the following line at the top:

require 'baby_bear/engine'

Then create baby_bear/lib/baby_bear/engine.rb from this template:

require 'rails'

module BabyBear
  class Engine < Rails::Engine
  end
end

We have a choice at this point: either we can namespace all of our engine’s application classes (controllers, models, etc.)—or not. Namespaces help prevent naming conflicts between your engine and the main application, and frankly, if you’re going to the trouble of developing an engine, you probably care about separation of responsibilities and reusability already. In any event, this tutorial will use namespaces.

So let’s add the following line to BabyBear::Engine:

isolate_namespace BabyBear

This creates a little more work for us up front because we’ll have to move our controllers, helpers, mailers, models, and views into their own baby_bear subdirectories.

mkdir app/controllers/baby_bear \
  app/helpers/baby_bear \
  app/mailers/baby_bear \
  app/models/baby_bear \
  app/views/baby_bear
mv app/controllers/application_controller.rb app/controllers/baby_bear/
mv app/helpers/application_helper.rb app/helpers/baby_bear/

Be sure to change ApplicationController to BabyBear::ApplicationController. Ditto for ApplicationHelper.

In order to make sure that the engine is running normally, we’ll need to create a page to test it. This is a good time to give you the bad news: engines can’t use the rails command. Sorry, no code generation for baby_bear. In practice, I’ve found that the benefits of engines outweigh this (rather disappointing) drawback, however.

touch app/controllers/baby_bear/index_controller.rb
mkdir app/views/baby_bear/index
touch app/views/baby_bear/index/index.html.erb

Here’s the controller…

module BabyBear
  class IndexController < ApplicationController
    def index_action
    end
  end
end

…and its corresponding view.

It works!

One last thing: we need a route. Add the following line to baby_bear’s config/routes.rb, in the main block:

root :to => 'index#index'

Finally, we’re ready to start WEBrick.

cd ../papa_bear
rails server

Are you ready for this?! Go to http://localhost:3000/baby-bear.

Finally, success! Hopefully, anyway. If not, be sure to double-check your steps and check out the code linked below. Good luck!

Download the code.


2
Dec 10

Git bisect is the best thing ever

I’ve raved about this command to coworkers before, but this morning after a bundle install with edge Rails our multi-database migrations broke, and I needed to figure out why. This had happened sometime between November 15 and December 2… on a less active codebase that might not be a big deal, but there were 204 commits to Rails in that time.

Enter git bisect!

$ cd rails/
local:rails matt$ git bisect start
local:rails matt$ git bisect bad
local:rails matt$ git bisect good 8124b2bc24b8312ee4d1ce2f133bf6e2dd87ad49
Bisecting: 204 revisions left to test after this (roughly 8 steps)
[51007f1dbafe029ed85b2a296736a00e6b24ec58] Previous version inaccurately suggested that
local:rails matt$ git bisect bad
Bisecting: 101 revisions left to test after this (roughly 7 steps)
[77440ec51ad28a7e63651f0976053584a7f58768] fixing assertions so error messages will be more helpful
local:rails matt$ git bisect good
Bisecting: 50 revisions left to test after this (roughly 6 steps)
[96b50a039276b4391ddf07b0a74850ce7bad6863] IrreversibleMigration is raised if we cannot invert the command
local:rails matt$ git bisect bad
Bisecting: 25 revisions left to test after this (roughly 5 steps)
[56c5820458fd3981161393c285cce67fdf35e60b] use shorter form for sql literals
local:rails matt$ git bisect bad
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[f14c2bf58204f488cbe589e077a124865ea595f5] Pass the view object as parameter to the handler. Useful if you need to access the lookup context or other information when compiling the template.
local:rails matt$ git bisect bad
Bisecting: 5 revisions left to test after this (roughly 3 steps)
[974ff0dd43826aa375417852356ceede1bd24cf2] singleton method added is no longer needed
local:rails matt$ git bisect bad
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[fe2f168d40385a0412f41c1a2a44a5536cada8df] fix warning during test execution
local:rails matt$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[0bea9fd6be1c82154d7b2d4adbfc690a2c1df297] schema migrations work as instances
local:rails matt$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[8b2f801ed8690dcbc61d62e6b3518efaac70a4a4] converted migrations to support instance methods
local:rails matt$ git bisect bad
8b2f801ed8690dcbc61d62e6b3518efaac70a4a4 is the first bad commit
commit 8b2f801ed8690dcbc61d62e6b3518efaac70a4a4
Author: Aaron Patterson <aaron.patterson@gmail.com>
Date:   Wed Nov 17 12:53:38 2010 -0800

    converted migrations to support instance methods

:040000 040000 8e4adf421f6b9d8e6d696ac9526319b8e71bb486 cf3def12c0e5c1e92393f7293a5c5c9637d5af8f M  activerecord

If it’s a relatively simple bug to isolate, look into git bisect run–it makes the process entirely automated.

(Oh, and by the way, if anyone is running into the same problem I did, ActiveRecord::Base.connection is now ActiveRecord::Base#connection. In other words, it’s now an instance method, not a class method.)


15
Nov 10

Edge Rails pro-tip!

When using edge Rails, you should use edge Arel and edge Rack, also. Otherwise you’ll get weird bugs.

gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'arel',  :git => 'git://github.com/rails/arel.git'
gem 'rack',  :git => 'git://github.com/rack/rack.git'

29
Oct 10

Edge Rails and Bundler

I needed some Rails 3.1 enhancements to Rails Engines for an in-development app, so I enabled Edge Rails in my Gemfile:

gem 'rails', :git => 'git://github.com/rails/rails.git'

Then I did what I normally do, sudo bundle install. But this broke the rails command—why?

I deleted the Git checkout and tried again:

$ sudo bundle install
Fetching git://github.com/rails/rails.git
remote: Counting objects: 195841, done.
remote: Compressing objects: 100% (42932/42932), done.
remote: Total 195841 (delta 151525), reused 195056 (delta 150929)
Receiving objects: 100% (195841/195841), 33.12 MiB | 2.05 MiB/s, done.
Resolving deltas: 100% (151525/151525), done.
Fetching source index for http://rubygems.org/
[...]
Using rails (3.1.0.beta) from git://github.com/rails/rails.git (at master)
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
$ bundle show
git://github.com/rails/rails.git (at master) is not checked out. Please run `bundle install`

Although Bundler has had some issues with remote Git repositories in the past, this one was entirely my fault: don’t use sudo bundle install. A simple check confirms this: sudo bundle show works like you’d expect.

So to recap: remove the checkout and bundle install (no sudo!) to fix this problem.