Rails 3 Generators: The Unusuals (part 1)
OK, we’ve seen how the most common generators have changed (some more than others) in Rails 3. Let’s look at a few more that’ve been around for a while, but haven’t been used quite as much.
Helper
[rails2] > ./script/generate helper Address
exists app/helpers/
exists test/unit/helpers/
create app/helpers/address_helper.rb
create test/unit/helpers/address_helper_test.rb
[rails3] > rails generate helper Address
create app/helpers/address_helper.rb
invoke test_unit
create test/unit/helpers/address_helper_test.rb
I probably should’ve included this one in the last post, since it gets invoked by the controller and resource generators. It’s pretty simple, regardless, just dropping the explicit existence checks (that’s the last time I mention this change, I swear!) and then creating a file and a test for it. As we saw before, the invoke test_unit line means that we could potentially replace the Test::Unit generator with an alternative like RSpec.
Mailer
[rails2] > ./script/generate mailer Account confirmation
exists app/models/
create app/views/account
exists test/unit/
create test/fixtures/account
create app/models/account.rb
create test/unit/account_test.rb
create app/views/account/confirmation.erb
create test/fixtures/account/confirmation
[rails3] > rails generate mailer Account confirmation
create app/mailers/account.rb
invoke erb
create app/views/account
create app/views/account/confirmation.text.erb
invoke test_unit
create test/functional/account_test.rb
create test/fixtures/account/confirmation
Let’s start with the easy stuff – you can replace the template engine for the mail (that’s the invoke erb piece) or the tests (invoke test_unit). Unlike the other generators we’ve seen, however, there isn’t an option to replace the fixture created here, as far as I can tell. Granted, mailer fixtures aren’t troublesome in the way that ORM fixtures are.
There’s a bigger change here too, however. Notice that instead of creating app/models/account.rb, in Rails 3 we’re getting app/mailers/account.rb. That’s right – we finally got a separate mailers directory! And the changes don’t stop there, since the ActionMailer API got a major overhaul, too, as you can see by comparing the generated files:
# Rails 2
class Account < ActionMailer::Base
def confirmation(sent_at = Time.now)
subject 'Account#confirmation'
recipients ''
from ''
sent_on sent_at
body :greeting => 'Hi,'
end
end
# Rails 3
class Account < ActionMailer::Base
default :from => "from@example.com"
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.actionmailer.account.confirmation.subject
#
def confirmation
@greeting = "Hi"
mail :to => "to@example.org"
end
end
Basically, mailers now work much more like controllers. Mikel Lindsaar wrote up the canonical explanation of these changes (which is fitting, since he’s responsible for them).
Observer
[rails2] > ./script/generate observer Account
exists app/models/
exists test/unit/
create app/models/account_observer.rb
create test/unit/account_observer_test.rb
[rails3] > rails generate observer Account
invoke active_record
create app/models/account_observer.rb
invoke test_unit
create test/unit/account_observer_test.rb
The observer generator is another straightforward port of the existing Rails 2 generator – nothing special to see here, please move along.
Metal
[rails2] > ./script/generate metal Accelerator
create app/metal
create app/metal/accelerator.rb
[rails3] > rails generate metal Accelerator
create app/metal/accelerator.rb
And here’s another that hasn’t changed much – including the complete lack of a test being generated for you. For shame, core team!
There is a minor difference in the generated metal files between Rails 2 and 3, by the way:
# Rails 2
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
class Accelerator
def self.call(env)
if env["PATH_INFO"] =~ /^\/accelerator/
[200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
end
# Rails 3
# Allow the metal piece to run in isolation
require File.expand_path('../../../config/environment', __FILE__) unless defined?(Rails)
class Accelerator
def self.call(env)
if env["PATH_INFO"] =~ /^\/accelerator/
[200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
else
[404, {"Content-Type" => "text/html", "X-Cascade" => "pass"}, ["Not Found"]]
end
end
end
Notice that in Rails 3, an additional header is being sent when the route isn’t matched - "X-Cascade" => "pass". This is something that came (as far as I can tell) from Sinatra, and is meant to allow other middlewares to keep trying to match the path if the metal doesn’t handle it.
I can’t help but wonder, though, about the relationship between these generated metal classes and the new ActionController::Metal class. Shouldn’t the generator be producing subclasses of the latter? Answers in the comments are welcome!
Next Time
As you’ve noticed, all the output blocks make these posts a little long. As a result, I’ve split up the less-common generators into this post and the next. Stay tuned for the updates to the session_migration, integration_test, performance_test, and plugin generators!
