Thursday, February 9, 2012

Adventures in moving from Rails 2.3.14 to 3.0: ActionMailer changes

So many deprecation notices, so many things to fix, when it comes to mailers.

After getting some of my model specs to run without issue, I decided to focus on the my mailer specs, as I new there were many things that had to change.

In the related mailer_spec

Assert_select and assert_select_email instead of have_tag, have_text
The first mailer spec I ran, everything blew up, because I had been using have_tag and have_text as matchers. I could not figure out how to get them to work in Rails 3, so switched to using assert_select and assert_select_email.

However, in rails 3.0, assert_select_email has a couple of problems. First, it assumes that emails will be multipart. This wasn't true in my case. It also assumes that email.body returns a string, which it does not, it returns a Body object.

There is a fix in rails 3.1 for this, so I borrowed that code and created an initializer with the following:

module ActionDispatch
  module Assertions
    module SelectorAssertions
      # this is fixed in Rails 3.1
      # in 3.0, if the email has no parts, it would always return true
      # also, the body needed to be converted to a string, as that is what HTML::Document.new expects
      def assert_select_email(email = nil, &block)
        deliveries = email ? [email] : ActionMailer::Base.deliveries
        assert !deliveries.empty?, "No e-mail in delivery list"

        for delivery in deliveries
          for part in (delivery.parts.empty? ? [delivery] : delivery.parts)
            if part["Content-Type"].to_s =~ /^text\/html\W/
              root = HTML::Document.new(part.body.to_s).root
              assert_select root, ":root", &block
            end
          end
        end
      end
    end
  end
end

Unlike the fix rails 3.1 fix (https://github.com/rails/rails/pull/2499), this also allows you to pass in an email object. Without this, if assert_select_email found more than one email in the deliveries list, and one of them didn't match your select criteria, this would fail, even if other emails in the deliveries list matched.

By passing in an email, you don't leave that to chance.

Changing all of the have_tag, with_tag, etc to assert_select statements was a bit of a pain, but it works well now. 

Change have_text to have_body_text

Move "deliver_" at the front of the mailer method call to ".deliver" at the end of the mailer method call

In the mailer.rb file

Change @body[:foo] = 'bar' assignments to @foo = 'bar'
To change the @body[:foo] = 'bar' assignment to @foo = 'bar', I used the following in TextMates Find in Project
Find: \@body\[:([a-z_1-9]*)\]
Replace: @$1
Use Regular Expressions: true

No comments:

Post a Comment