Close and Go BackBack to Viget

Rails 3 Generators: The Unusuals (part 1)

Ben Scofield
Ben Scofield, Former Staffer, February 19, 2010

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!

blog comments powered by Disqus

We're the Developers

at Viget Labs. We write about web development trends, tips, best practices, industry events, and our projects — all with an emphasis on Ruby on Rails.

Contact Us

Have any questions, comments, ideas, or secrets to share? Let us know.


How many minutes in an hour?

Sorry, you need to have Javascript enabled to use this form. (Don't blame us, blame the spammers!) If you'd like to contact us, please visit our Contact page.