Chapter 24 - Action Mailer PDF

Title Chapter 24 - Action Mailer
Author USER COMPANY
Course Agile development processes
Institution Chalmers tekniska högskola
Pages 16
File Size 260.2 KB
File Type PDF
Total Downloads 72
Total Views 146

Summary

Action Mailer...


Description

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

S EN DING E- MA IL

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.

569

S EN DING E- MA IL

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

570

S EN DING E- MA IL

bunch of test-related files—we’ll look into these later in Section 24.3, Testing E-mail, on page 580.) 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"

571

S EN DING E- MA IL @recipients = array or string

One or more recipient e-mail addresses. These may be simple addresses, such as [email protected], or some identifying phrase followed by the e-mail address in angle brackets. @recipients = [ "[email protected]", "Dave Thomas " ]

@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 email). 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 Thank you for your recent order from The Pragmatic Store. You ordered the following items: "./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

572

S EN DING E- MA IL

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 = '[email protected]' @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 => "" + email.encoded + "") 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.

573

S EN DING E- MA IL 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: [email protected] To: [email protected] 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 sent method in OrderMailer. (In reality, there’s so much commonality between this method and the original confirm method that we’d probably refactor both 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 = '[email protected]' @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

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

574

S EN DING E- MA IL

QtyDescription "./html_line_item" , :collection => @order.line_items) %>

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.rht ml

×

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

575

S EN DING E- MA IL

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 = @recipients = @from = @sent_on = @body["order" ] = end

"Pragmatic Order: Give us your thoughts" order.email '[email protected]' Time.now order

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

576

S EN DING E- MA IL

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

Dear 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

A Pragmatic Survey Dear You recently placed an order with our store. We were wondering if you'd mind taking the time to visit our survey site and rate your experience. Many thanks.

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

577

S EN DING E- MA IL

The template is the same as the original sent.rhtml file. Download e1/mailer/app/views/order_mailer/sent.rhtml

Pragmatic Order Shipped This is just to let you know that we've shipped your recent order: QtyDescription "./html_line_item" , :collection => @order.line_items) %>

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 = '[email protected]' @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" "image/png" when ".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.

578

R ECEIVING E- MA IL

24.2 Receiving E-mail Action Mai...


Similar Free PDFs