Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 2nd Edition (2006).pdf
Скачиваний:
30
Добавлен:
17.08.2013
Размер:
6.23 Mб
Скачать

Chapter 24

Action Mailer

Action Mailer is a simple Rails component that allows your applications to send and receive e-mail. Using Action Mailer, your online store could send out order confirmations, and your incident-tracking system could automatically log problems submitted to a particular e-mail address.

24.1Sending E-mail

Before you start sending e-mail, you’ll need to configure Action Mailer. Its default configuration works on some hosts, but you’ll want to create your own configuration anyway, just to make it an explicit part of your application.

E-mail Configuration

E-mail configuration is part of a Rails application’s environment. If you want to use the same configuration for development, testing, and production, add the configuration to environment.rb in the config directory; otherwise, add different configurations to the appropriate files in the config/environments directory.

You first have to decide how you want mail delivered.

config.action_mailer.delivery_method = :smtp | :sendmail | :test

The :smtp and :sendmail options are used when you want Action Mailer to attempt to deliver e-mail. You’ll clearly want to use one of these methods in production.

The :test setting is great for unit and functional testing. E-mail will not be delivered but instead will be appended to an array (accessible via the attribute ActionMailer::Base.deliveries). This is the default delivery method in the test environment. Interestingly, though, the default in development mode is :smtp. If you want your development code to deliver e-mail, this is good. If you’d rather disable e-mail delivery in development mode, edit the file development.rb in the directory config/environments, and add the line

SENDING E-MAIL 568

config.action_mailer.delivery_method = :test

The :sendmail setting delegates mail delivery to your local system’s sendmail program, which is assumed to be in /usr/sbin. This delivery mechanism is not particularly portable, because sendmail is not always installed in this directory on different operating systems. It also relies on your local sendmail supporting the -i and -t command options.

You achieve more portability by leaving this option at its default value of :smtp. If you do so, though, you’ll need also to specify some additional configuration to tell Action Mailer where to find an SMTP server to handle your outgoing e-mail. This may be the machine running your web application, or it may be a separate box (perhaps at your ISP if you’re running Rails in a noncorporate environment). Your system administrator will be able to give you the settings for these parameters. You may also be able to determine them from your own mail client’s configuration.

config.action_mailer.server_settings = {

:address

=> "domain.of.smtp.host.net",

:port

=>

25,

:domain

=>

"domain.of.sender.net" ,

:authentication => :login, :user_name => "dave", :password => "secret"

}

:address => and :port =>

Determines the address and port of the SMTP server you’ll be using. These default to localhost and 25, respectively.

:domain =>

The domain that the mailer should use when identifying itself to the server. This is called the HELO domain (because HELO is the command the client sends to the server to initiate a connection). You should normally use the top-level domain name of the machine sending the e-mail, but this depends on the settings of your SMTP server (some don’t check, and some check to try to reduce spam and so-called open-relay issues).

:authentication =>

One of :plain, :login, or :cram_md5. Your server administrator will help choose the right option. There is currently no way of using TLS (SSL) to connect to a mail server from Rails. This parameter should be omitted if your server does not require authentication. If you do omit this parameter, also omit (or comment out) the :user_name and :password options.

:user_name => and :password =>

Required if :authentication is set.

Report erratum

SENDING E-MAIL 569

Other configuration options apply to all delivery mechanisms.

config.action_mailer.perform_deliveries = true | false

If perform_deliveries is true (the default), mail will be delivered normally. If false, requests to deliver mail will be silently ignored. This might be useful to disable e-mail while testing.

config.action_mailer.raise_delivery_errors = true | false

If raise_delivery_errors is true (the default), any errors that occur when initially sending the e-mail will raise an exception back to your application. If false, errors will be ignored. Remember that not all e-mail errors are immediate—an e-mail might bounce three days after you send it, and your application will (you hope) have moved on by then.

Set the character set used for new e-mail with

config.action_mailer.default_charset = "utf-8"

As with all configuration changes, you’ll need to restart your application if you make changes to any of the environment files.

Sending E-mail

Now that we’ve got everything configured, let’s write some code to send e-mails.

By now you shouldn’t be surprised that Rails has a generator script to create mailers. What might be surprising is where it creates them. In Rails, a mailer is a class that’s stored in the app/models directory. It contains one or more methods, each method corresponding to an e-mail template. To create the body of the e-mail, these methods in turn use views (in just the same way that controller actions use views to create HTML and XML). So, let’s create a mailer for our store application. We’ll use it to send two different types of e-mail: one when an order is placed and a second when the order ships. The generate mailer script takes the name of the mailer class, along with the names of the e-mail action methods.

depot> ruby script/generate mailer OrderMailer confirm sent

exists

app/models/

exists

app/views/order_mailer

exists

test/unit/

create

test/fixtures/order_mailer

create

app/models/order_mailer.rb

create test/unit/order_mailer_test.rb

create

app/views/order_mailer/confirm.rhtml

create

test/fixtures/order_mailer/confirm

create

app/views/order_mailer/sent.rhtml

create

test/fixtures/order_mailer/sent

Notice that we’ve created an OrderMailer class in app/models and two template files, one for each e-mail type, in app/views/order_mailer. (We also created a

Report erratum

SENDING E-MAIL 570

bunch of test-related files—we’ll look into these later in Section 24.3, Testing E-mail, on page 579.)

Each method in the mailer class is responsible for setting up the environment for sending a particular e-mail. It does this by setting up instance variables containing data for the e-mail’s header and body. Let’s look at an example before going into the details. Here’s the code that was generated for our OrderMailer class.

class OrderMailer < ActionMailer::Base

def confirm(sent_at = Time.now)

@subject

= 'OrderMailer#confirm'

@body

= {}

@recipients

= ''

@from

= ''

@sent_on

= sent_at

@headers

= {}

end

 

def sent(sent_at = Time.now) @subject = 'OrderMailer#sent'

# ... same as above ...

end end

Apart from @body, which we’ll discuss in a second, the instance variables all set up the envelope and header of the e-mail that’s to be created:

@bcc = array or string

Blind-copy recipients, using the same format as @recipients.

@cc = array or string

Carbon-copy recipients, using the same format as @recipients.

@charset = string

The character set used in the e-mail’s Content-Type header. Defaults to the default_charset attribute in server_settings, or "utf-8".

@from = array or string

One or more e-mail addresses to appear on the From: line, using the same format as @recipients. You’ll probably want to use the same domain name in these addresses as the domain you configured in server_settings.

@headers = hash

A hash of header name/value pairs, used to add arbitrary header lines to the e-mail.

@headers["Organization" ] = "Pragmatic Programmers, LLC"

Report erratum

SENDING E-MAIL 571

@recipients = array or string

One or more recipient e-mail addresses. These may be simple addresses, such as dave@pragprog.com, or some identifying phrase followed by the e-mail address in angle brackets.

@recipients = [ "andy@pragprog.com", "Dave Thomas <dave@pragprog.com>" ]

@sent_on = time

A Time object that sets the e-mail’s Date: header. If not specified, the current date and time will be used.

@subject = string

The subject line for the e-mail.

The @body is a hash, used to pass values to the template that contains the e-mail. We’ll see how that works shortly.

E-mail Templates

The generate script created two e-mail templates in app/views/order_mailer, one for each action in the OrderMailer class. These are regular ERb rhtml files. We’ll use them to create plain-text e-mails (we’ll see later how to create HTML e- mail). As with the templates we use to create our application’s web pages, the files contain a combination of static text and dynamic content. We can customize the template in confirm.rhtml; this is the e-mail that is sent to confirm an order.

Download e1/mailer/app/views/order_mailer/confirm.rhtml

Dear <%= @order.name %>

Thank you for your recent

You ordered the following

order from The Pragmatic Store.

items:

<%= render(:partial => "./line_item", :collection => @order.line_items) %>

We'll send you a separate e-mail when your order ships.

There’s one small wrinkle in this template. We have to give render the explicit path to the template (the leading ./) because we’re not invoking the view from a real controller and Rails can’t guess the default location.

The partial template that renders a line item formats a single line with the item quantity and the title. Because we’re in a template, all the regular helper methods, such as truncate, are available.

Download e1/mailer/app/views/order_mailer/_line_item.rhtml

<%= sprintf("%2d x %s", line_item.quantity,

truncate(line_item.product.title, 50)) %>

Report erratum

SENDING E-MAIL 572

We now have to go back and fill in the confirm method in the OrderMailer class.

Download e1/mailer/app/models/order_mailer.rb

class OrderMailer < ActionMailer::Base def confirm(order)

@subject

= "Pragmatic Store Order Confirmation"

@recipients

= order.email

@from

=

'orders@pragprog.com'

@sent_on

=

Time.now

@body["order"]

= order

end end

Now we get to see what the @body hash does: values set into it are available as instance variables in the template. In this case, the order object will be stored into @order.

Generating E-mails

Now that we have our template set up and our mailer method defined, we can use them in our regular controllers to create and/or send e-mails. However, we don’t call the method directly. That’s because there are two different ways you can create e-mail from within Rails: you can create an e-mail as an object, or you can deliver an e-mail to its recipients. To access these functions, we call class methods called create_xxx and deliver_xxx, where xxx is the name of the instance method we wrote in OrderMailer. We pass to these class methods the parameter(s) that we’d like our instance methods to receive. To send an order confirmation e-mail, for example, we could call

OrderMailer.deliver_confirm(order)

To experiment with this without actually sending any e-mails, we can write a simple action that creates an e-mail and displays its contents in a browser window.

Download e1/mailer/app/controllers/test_controller.rb

class TestController < ApplicationController def create_order

order = Order.find_by_name("Dave Thomas") email = OrderMailer.create_confirm(order)

render(:text => "<pre>" + email.encoded + "</pre>") end

end

The create_confirm call invokes our confirm instance method to set up the details of an e-mail. Our template is used to generate the body text. The body, along with the header information, gets added to a new e-mail object, which create_confirm returns. The object is an instance of class TMail::Mail.1 The

1. TMail is Minero Aoki’s excellent e-mail library; a version ships with Rails.

Report erratum

SENDING E-MAIL 573

email.encoded call returns the text of the e-mail we just created: our browser will show something like

Date: Thu, 12 Oct 2006 12:17:36 -0500

From: orders@pragprog.com

To: dave@pragprog.com

Subject: Pragmatic Store Order Confirmation

Mime-Version: 1.0

Content-Type: text/plain; charset=utf-8

Dear Dave Thomas

Thank you for your recent order from The Pragmatic Store.

You ordered the following items:

1 x Programming Ruby, 2nd Edition

1 x Pragmatic Project Automation

We'll send you a separate e-mail when your order ships.

If we’d wanted to send the e-mail, rather than just create an e-mail object, we could have called OrderMailer.deliver_confirm(order).

Delivering HTML-Format E-mail

One way of creating HTML e-mail is to create a template that generates HTML for the e-mail body and then set the content type on the TMail::Mail object to text/html before delivering the message.

We’ll start by implementing the so much commonality between that we’d probably refactor both

sent method in OrderMailer. (In reality, there’s this method and the original confirm method to use a shared helper.)

Download e1/mailer/app/models/order_mailer.rb

class OrderMailer

< ActionMailer::Base

def sent(order)

 

@subject

= "Pragmatic Order Shipped"

@recipients

= order.email

@from

= 'orders@pragprog.com'

@sent_on

= Time.now

@body["order"] = order end

end

Next, we’ll write the sent.rhtml template.

Download e1/mailer/app/views/order_mailer/sent.rhtml

<h3>Pragmatic Order Shipped</h3> <p>

This is just to let you know that we've shipped your recent order:

</p>

Report erratum

SENDING E-MAIL 574

<table>

<tr><th colspan="2">Qty</th><th>Description</th></tr>

<%= render(:partial => "./html_line_item", :collection => @order.line_items) %>

</table>

We’ll need a new partial template that generates table rows. This goes in the file _html_line_item.rhtml.

Download e1/mailer/app/views/order_mailer/_html_line_item.rhtml

<tr>

<td><%= html_line_item.quantity %></td> <td>×</td>

<td><%= html_line_item.product.title %></td> </tr>

And finally we’ll test this using an action method that renders the e-mail, sets the content type to text/html, and calls the mailer to deliver it.

Download e1/mailer/app/controllers/test_controller.rb

class TestController < ApplicationController def ship_order

order = Order.find_by_name("Dave Thomas") email = OrderMailer.create_sent(order) email.set_content_type("text/html") OrderMailer.deliver(email)

render(:text => "Thank you...") end

end

The resulting e-mail will look something like Figure 24.1, on the next page.

Delivering Multiple Content Types

Some people prefer receiving e-mail in plain-text format, while others like the look of an HTML e-mail. Rails makes it easy to send e-mail messages that contain alternative content formats, allowing the user (or their e-mail client) to decide what they’d prefer to view.

In the preceding section, we created an HTML e-mail by generating HTML content and then setting the content type to text/html. It turns out that Rails has a convention that will do all this, and more, automatically.

The view file for our sent action was called sent.rhtml. This is the standard Rails naming convention. But, for e-mail templates, there’s a little bit more naming magic. If you name a template file

name.content.type.rhtml

Rails will automatically set the content type of the e-mail to the content type in the filename. For our previous example, we could have set the view filename to sent.text.html.rhtml, and Rails would have sent it as an HTML e-mail automatically. But there’s more. If you create multiple templates with the

Report erratum

SENDING E-MAIL 575

Figure 24.1: An HTML-Format E-mail

same name but with different content types embedded in their filenames, Rails will send all of them in one e-mail, arranging the content so that the e-mail client will be able to distinguish each. Thus by creating sent.text.plain.rhtml and sent.text.html.rhtml templates, we could give the user the option of viewing our e-mail as either text or HTML.

Let’s try this. We’ll set up a new action.

Download e1/mailer/app/controllers/test_controller.rb

def survey

order = Order.find_by_name("Dave Thomas") email = OrderMailer.deliver_survey(order) render(:text => "E-Mail sent")

end

We’ll add support for the survey to order_mailer.rb in the app/models directory.

Download e1/mailer/app/models/order_mailer.rb

def survey(order)

@subject

= "Pragmatic Order: Give us your thoughts"

@recipients

= order.email

@from

=

'orders@pragprog.com'

@sent_on

=

Time.now

@body["order"]

= order

end

And we’ll create two templates. Here’s the plain-text version, in the file survey.text.plain.rhtml.

Report erratum

SENDING E-MAIL 576

Download e1/mailer/app/views/order_mailer/survey.text.plain.rhtml

Dear <%= @order.name %>

You recently placed an order with our store.

We were wondering if you'd mind taking the time to visit http://some.survey.site and rate your experience.

Many thanks

And here’s survey.text.html.rhtml, the template that generates the HTML e-mail.

Download e1/mailer/app/views/order_mailer/survey.text.html.rhtml

<h3>A Pragmatic Survey</h3>

<p>

Dear <%= @order.name %>

</p>

<p>

You recently placed an order with our store.

</p>

<p>

We were wondering if you'd mind taking the time to

visit <a href="http://some.survey.site" >our survey site</a> and rate your experience.

<p>

<p>

Many thanks.

</p>

You can also use the part method within an Action Mailer method to create multiple content types explicitly. See the Rails API documentation for ActionMailer::Base for details.

Sending Attachments

When you send e-mail with multiple content types, Rails actually creates a separate e-mail attachment for each. This all happens behind the scenes. However, you can also manually add your own attachments to e-mails.

Let’s create a different version of our confirmation e-mail that sends cover images as attachments. The action is called ship_with_images.

Download e1/mailer/app/controllers/test_controller.rb

def ship_with_images

order = Order.find_by_name("Dave Thomas")

email = OrderMailer.deliver_ship_with_images(order) render(:text => "E-Mail sent")

end

Report erratum

SENDING E-MAIL 577

The template is the same as the original sent.rhtml file.

Download e1/mailer/app/views/order_mailer/sent.rhtml

<h3>Pragmatic Order Shipped</h3> <p>

This is just to let you know that we've shipped your recent order:

</p>

<table>

<tr><th colspan="2">Qty</th><th>Description</th></tr>

<%= render(:partial => "./html_line_item", :collection => @order.line_items) %>

</table>

All the interesting work takes place in the ship_with_images method in the mailer class.

Download e1/mailer/app/models/order_mailer.rb

def ship_with_images(order)

@subject

= "Pragmatic Order Shipped"

@recipients

= order.email

@from

=

'orders@pragprog.com'

@sent_on

=

Time.now

@body["order"]

= order

part :content_type => "text/html",

:body => render_message("sent" , :order => order)

order.line_items.each do |li| image = li.product.image_location

content_type = case File.extname(image)

when ".jpg", ".jpeg";

"image/jpeg"

when ".png";

"image/png"

when ".gif";

"image/gif"

else;

"application/octet-stream"

end

 

attachment :content_type => content_type,

:body

=> File.read(File.join("public" , image)),

:filename

=> File.basename(image)

end end

Notice that this time we explicitly render the message using a part directive, forcing its type to be text/html and its body to be the result of rendering the template.2 We then loop over the line items in the order. For each, we determine the name of the image file, construct the mime type based on the file’s extension, and add the file as an inline attachment.

2. At the time of writing, there’s a minor bug in Rails. If a message has attachments, Rails will not render the default template for the message if you name it using the xxx.text.html.rhtml convention. Adding the content explicitly using part works fine.

Report erratum

RECEIVING E-MAIL 578

24.2Receiving E-mail

Action Mailer makes it easy to write Rails applications that handle incoming e-mail. Unfortunately, you also need to find a way of getting appropriate e- mails from your server environment and injecting them into the application; this requires a bit more work.

The easy part is handling an e-mail within your application. In your Action Mailer class, write an instance method called receive that takes a single parameter. This parameter will be a TMail::Mail object corresponding to the incoming e-mail. You can extract fields, the body text, and/or attachments and use them in your application.

For example, a bug-tracking system might accept trouble tickets by e-mail. From each e-mail, it constructs a Ticket model object containing the basic ticket information. If the e-mail contains attachments, each will be copied into a new TicketCollateral object, which is associated with the new ticket.

Download e1/mailer/app/models/incoming_ticket_handler.rb

class IncomingTicketHandler < ActionMailer::Base

def receive(email) ticket = Ticket.new

ticket.from_email = email.from[0] ticket.initial_report = email.body if email.has_attachments?

email.attachments.each

do |attachment|

collateral = TicketCollateral.new(

:name

=>

attachment.original_filename,

:body

=>

attachment.read)

ticket.ticket_collaterals << collateral end

end ticket.save

end end

So now we have the problem of feeding an e-mail received by our server computer into the receive instance method of our IncomingTicketHandler. This problem is actually two problems in one: first we have to arrange to intercept the reception of e-mails that meet some kind of criteria, and then we have to feed those e-mails into our application.

If you have control over the configuration of your mail server (such as a Postfix or sendmail installation on Unix-based systems), you might be able to arrange to run a script when an e-mail addressed to a particular mailbox or virtual host is received. Mail systems are complex, though, and we don’t have room to go into all the possible configuration permutations here. There’s a good

Report erratum

TESTING E-MAIL 579

introduction to this on the Ruby development wiki.3

If you don’t have this kind of system-level access but you are on a Unix system, you could intercept e-mail at the user level by adding a rule to your .procmailrc file. We’ll see an example of this shortly.

The objective of intercepting incoming e-mail is to pass it to our application. To do this, we use the Rails runner facility. This allows us to invoke code within our application’s codebase without going through the Web. Instead, the runner loads up the application in a separate process and invokes code that we specify in the application.

All of the normal techniques for intercepting incoming e-mail end up running a command, passing that command the content of the e-mail as standard input. If we make the Rails runner script the command that’s invoked whenever an e-mail arrives, we can arrange to pass that e-mail into our application’s e- mail handling code. For example, using procmail-based interception, we could write a rule that looks something like the example that follows. Using the arcane syntax of procmail, this rule copies any incoming e-mail whose subject line contains Bug Report through our runner script.

RUBY=/Users/dave/ruby1.8/bin/ruby

TICKET_APP_DIR=/Users/dave/Work/BS2/titles/RAILS/Book/code/e1/mailer

HANDLER='IncomingTicketHandler.receive(STDIN.read)'

:0 c

* ^Subject:.*Bug Report.*

| cd $TICKET_APP_DIR && $RUBY script/runner $HANDLER

The receive class method is available to all Action Mailer classes. It takes the e-mail text passed as a parameter, parses it into a TMail object, creates a new instance of the receiver’s class, and passes the TMail object to the receive instance method in that class. This is the method we wrote on the preceding page. The upshot is that an e-mail received from the outside world ends up creating a Rails model object, which in turn stores a new trouble ticket in the database.

24.3Testing E-mail

There are two levels of e-mail testing. At the unit test level you can verify that your Action Mailer classes correctly generate e-mails. At the functional level, you can test that your application sends these e-mails when you expect it to send them.

3. http://wiki.rubyonrails.com/rails/show/HowToReceiveEmailsWithActionMailer

Report erratum

TESTING E-MAIL 580

Unit Testing E-mail

When we used the generate script to create our order mailer, it automatically constructed a corresponding order_mailer_test.rb file in the application’s test/unit directory. If you were to look at this file, you’d see that it is fairly complex. That’s because it lets you read the expected content of e-mails from fixture files and compare this content to the e-mail produced by your mailer class. However, this is fairly fragile testing. Anytime you change the template used to generate an e-mail, you’ll need to change the corresponding fixture.

If exact testing of the e-mail content is important to you, then use the pregenerated test class. Create the expected content in a subdirectory of the test/fixtures directory named for the test (so our OrderMailer fixtures would be in test/fixtures/order_mailer). Use the read_fixture method included in the generated code to read in a particular fixture file and compare it with the e-mail generated by your model.

However, I prefer something simpler. In the same way that I don’t test every byte of the web pages produced by templates, I won’t normally bother to test the entire content of a generated e-mail. Instead, I test the part that’s likely to break: the dynamic content. This simplifies the unit test code and makes it more resilient to small changes in the template. Here’s a typical e-mail unit test.

Download e1/mailer/test/unit/order_mailer_test.rb

require File.dirname(__FILE__) + '/../test_helper' require 'order_mailer'

class OrderMailerTest < Test::Unit::TestCase

def setup

@order = Order.new(:name =>"Dave Thomas", :email => "dave@pragprog.com") end

def test_confirm

response = OrderMailer.create_confirm(@order) assert_equal("Pragmatic Store Order Confirmation" , response.subject) assert_equal("dave@pragprog.com", response.to[0])

assert_match(/Dear Dave Thomas/, response.body) end

end

The setup method creates an order object for the mail sender to use. In the test method we get the mail class to create (but not to send) an e-mail, and we use assertions to verify that the dynamic content is what we expect. Note the use of assert_match to validate just part of the body content.

Report erratum

TESTING E-MAIL 581

Functional Testing of E-mail

Now that we know that e-mails can be created for orders, we’d like to make sure that our application sends the correct e-mail at the right time. This is a job for functional testing.

Let’s start by generating a new controller for our application.

depot> ruby script/generate controller Order confirm

We’ll implement the single action, confirm, which sends the confirmation e-mail for a new order.

Download e1/mailer/app/controllers/order_controller.rb

class OrderController < ApplicationController def confirm

order = Order.find(params[:id]) OrderMailer.deliver_confirm(order) redirect_to(:action => :index)

end end

We saw how Rails constructs a stub functional test for generated controllers back in Section 13.3, Functional Testing of Controllers, on page 197. We’ll add our mail testing to this generated test.

Action Mailer does not deliver e-mail in the test environment. Instead, it adds each e-mail it generates to an array, ActionMailer::base.deliveries. We’ll use this to get at the e-mail generated by our controller. We’ll add a couple of lines to the generated test’s setup method. One line aliases this array to the more manageable name @emails. The second clears the array at the start of each test.

Download e1/mailer/test/functional/order_controller_test.rb

@emails

= ActionMailer::Base.deliveries

@emails.clear

We’ll also need a fixture holding a sample order. We’ll create a file called orders.yml in the test/fixtures directory.

Download e1/mailer/test/fixtures/orders.yml

daves_order: id: 1

name: Dave Thomas address: 123 Main St email: dave@pragprog.com

Now we can write a test for our action. Here’s the full source for the test class.

Download e1/mailer/test/functional/order_controller_test.rb

require File.dirname(__FILE__) + '/../test_helper' require 'order_controller'

Report erratum

TESTING E-MAIL 582

# Re-raise errors caught by the controller.

class OrderController; def rescue_action(e) raise e end; end

# continued...

class OrderControllerTest < Test::Unit::TestCase

fixtures :orders

def setup

 

 

@controller

= OrderController.new

@request

= ActionController::TestRequest.new

@response

=

ActionController::TestResponse.new

@emails

=

ActionMailer::Base.deliveries

@emails.clear end

def test_confirm

get(:confirm, :id => orders(:daves_order).id) assert_redirected_to(:action => :index) assert_equal(1, @emails.size)

email = @emails.first

assert_equal("Pragmatic Store Order Confirmation" , email.subject) assert_equal("dave@pragprog.com", email.to[0])

assert_match(/Dear Dave Thomas/, email.body) end

end

It uses the @emails alias to access the array of e-mails generated by Action Mailer since the test started running. Having checked that exactly one e-mail is in the list, it then validates the contents are what we expect.

We can run this test either by using the test_functional target of rake or by executing the script directly.

depot> ruby test/functional/order_controller_test.rb

Report erratum