Rails 3 Generators: The Unusuals (part 1)

Ben Scofield, Former Viget

Article Category: #Code

Posted on

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!

Related Articles