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

Chapter 19

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.

19.1 Sending 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.

ActionMailer::Base.delivery_method = :smtp | :sendmail | :test

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 as ActionMailer::Base.deliveries). This is the default delivery method in the test environment.

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

Prepared exclusively for Rida Al Barazi

SENDING E-MAIL 400

not particularly portable, as 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.

ActionMailer::Base.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 socalled 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.

:user_name => and :password =>

Required if :authentication is set.

Other configuration options apply regardless of the delivery mechanism chosen.

ActionMailer::Base.perform_deliveries = true | false

Prepared exclusively for Rida Al Barazi

Report erratum

SENDING E-MAIL 401

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.

ActionMailer::Base.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 four days after you send it, and your application will (you hope) have moved on by then.

ActionMailer::Base.default_charset = "utf-8"

The character set used for new e-mail.

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/

create

app/views/order_mailer

exists

test/unit/

create

test/fixtures/order_mailers

create

app/models/order_mailer.rb

create test/unit/order_mailer_test.rb

create

app/views/order_mailer/confirm.rhtml

create

test/fixtures/order_mailers/confirm

create

app/views/order_mailer/sent.rhtml

create

test/fixtures/order_mailers/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 bunch of test-related files—we’ll look into these later in Section 19.3, Testing E-mail, on page 408).

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

Prepared exclusively for Rida Al Barazi

Report erratum

SENDING E-MAIL 402

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'

@recipients =

''

@from

=

''

@sent_on

=

sent_at

@headers

=

{}

@body

=

{}

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 characterset 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 is @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"

@recipients = array or string

One or more e-mail addresses for recipients. 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>" ]

Prepared exclusively for Rida Al Barazi

Report erratum

SENDING E-MAIL 403

@sent_on = time

ATime 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 cre-

 

ate 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 con-

 

tent. Here’s the template in confirm.rhtml that is sent to confirm an e-mail.

File 147

Dear <%= @order.name %>

 

Thank you for your recent order from The Pragmatic Store.

 

You ordered the following 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 ./) as 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.

File 146

<%= sprintf("%2d x %s",

 

line_item.quantity,

 

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

 

We now have to go back and fill in the confirm( ) method in the OrderMailer

 

class.

 

File 144

class OrderMailer < ActionMailer::Base

 

def confirm(order)

 

@subject

= "Pragmatic Store Order Confirmation"

 

@recipients

= order.email

 

@from

= 'orders@pragprog.com'

@body["order"] = order end

end

Prepared exclusively for Rida Al Barazi

Report erratum

 

SENDING E-MAIL

404

 

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 emails.

 

 

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 recipents. 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.

 

File 142

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 email.encoded( ) call returns the text of the e-mail we just created: our browser will show something like

Date: Fri, 29 Apr 2005 08:11:38 -0500

From: orders@pragprog.com

To: dave@pragprog.com

Subject: Pragmatic Store Order Confirmation

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

Dear Dave Thomas

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

Prepared exclusively for Rida Al Barazi

Report erratum

 

 

SENDING E-MAIL

405

 

Thank you for your recent order from The Pragmatic Store.

 

 

You ordered the following items:

 

 

1 x Programming Ruby, 2nd Edition

 

 

2 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

 

 

The simplest 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 sent( ) method in OrderMailer. (In reality,

 

 

there’s so much commonality between this method and the original con-

 

 

firm( ) method that we’d probably refactor both to use a shared helper.)

 

File 144

class OrderMailer < ActionMailer::Base

 

 

def sent(order)

 

 

 

@subject

= "Pragmatic Order Shipped"

 

 

@recipients

= order.email

 

 

@from

= 'orders@pragprog.com'

 

 

@body["order"] = order

 

 

end

 

 

 

end

 

 

 

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

 

File 148

<h3>Pragmatic Order Shipped</h3>

 

 

<p>

 

 

 

This is just to let you know that we've

 

 

shipped your recent order:

 

 

</p>

 

 

 

<table>

 

 

 

<tr><th>Qty</th><th></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.

 

File 145

<tr>

 

 

 

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

 

<td</td>

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

</tr>

Prepared exclusively for Rida Al Barazi

Report erratum