Iteration I1: Sending Confirmation Emails

Sending email in Rails has three basic parts: configuring how email is to be sent, determining when to send the email, and specifying what you want to say. We’ll cover each of these three in turn.

Configuring Email

Email configuration is part of a Rails application’s environment and involves a Rails.application.configure block. 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.

Inside the block, you need to have one or more statements. You first have to decide how you want mail delivered:

 config.​action_mailer​.​delivery_method​ = ​:smtp

Alternatives to :smtp include :sendmail and :test.

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

The :test setting is great for unit and functional testing, which we’ll make use of in Testing Email. Email won’t be delivered; instead, it’ll be appended to an array (accessible via the ActionMailer::Base.deliveries attribute). This is the default delivery method in the test environment. Interestingly, though, the default in development mode is :smtp. If you want Rails to deliver email during the development of your application, this is good. If you’d rather disable email delivery in development mode, edit the development.rb file in the config/environments directory and add the following lines:

 Rails.​application​.​configure​ ​do
  config.​action_mailer​.​delivery_method​ = ​:test
 end

The :sendmail setting delegates mail delivery to your local system’s sendmail program, which is assumed to be in /usr/sbin. This delivery mechanism isn’t particularly portable, because sendmail isn’t always installed in this directory for every operating system. 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, you’ll need also to specify some additional configuration to tell Action Mailer where to find an SMTP server to handle your outgoing email. This can be the machine running your web application, or it can 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.

The following are typical settings for Gmail: adapt them as you need.

 Rails.​application​.​configure​ ​do
  config.​action_mailer​.​delivery_method​ = ​:smtp
 
  config.​action_mailer​.​smtp_settings​ = {
 address: ​​"smtp.gmail.com"​,
 port: ​587,
 domain: ​​"domain.of.sender.net"​,
 authentication: ​​"plain"​,
 user_name: ​​"dave"​,
 password: ​​"secret"​,
 enable_starttls_auto: ​​true
  }
 end

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

Sending Email

Now that we have everything configured, let’s write some code to send emails.

By now you shouldn’t be surprised that Rails has a generator script to create mailers. In Rails, a mailer is a class that’s stored in the app/mailers directory. It contains one or more methods, with each method corresponding to an email template. To create the body of the email, these methods in turn use views (in 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 email: one when an order is placed and a second when the order ships. The rails generate mailer command takes the name of the mailer class, along with the names of the email action methods:

 depot>​​ ​​bin/rails​​ ​​generate​​ ​​mailer​​ ​​Order​​ ​​received​​ ​​shipped
  create app/mailers/order.rb
  create app/mailers/order_mailer.rb
  invoke erb
  create app/views/order_mailer
  identical app/views/layouts/mailer.text.erb
  identical app/views/layouts/mailer.html.erb
  create app/views/order_mailer/received.text.erb
  create app/views/order_mailer/received.html.erb
  create app/views/order_mailer/shipped.text.erb
  create app/views/order_mailer/shipped.html.erb
  invoke test_unit
  create test/mailers/order_mailer_test.rb
  create test/mailers/previews/order_mailer_preview.rb

Notice that we create an OrderMailer class in app/mailers and two template files, one for each email type, in app/views/order. (We also create a test file; we’ll look into this in Testing Email.)

Each method in the mailer class is responsible for setting up the environment for sending an email. Let’s look at an example before going into detail. Here’s the code that was generated for our OrderMailer class, with one default changed:

 class​ OrderMailer < ApplicationMailer
» default ​from: ​​'Sam Ruby <[email protected]>'
 
 # Subject can be set in your I18n file at config/locales/en.yml
 # with the following lookup:
 #
 # en.order_mailer.received.subject
 #
 def​ ​received
  @greeting = ​"Hi"
 
  mail ​to: ​​"[email protected]"
 end
 
 # Subject can be set in your I18n file at config/locales/en.yml
 # with the following lookup:
 #
 # en.order_mailer.shipped.subject
 #
 def​ ​shipped
  @greeting = ​"Hi"
 
  mail ​to: ​​"[email protected]"
 end
 end

If you’re thinking to yourself that this looks like a controller, that’s because it does. It includes one method per action. Instead of a call to render, there’s a call to mail. This method accepts a number of parameters including :to (as shown), :cc, :from, and :subject, each of which does pretty much what you’d expect it to do. Values that are common to all mail calls in the mailer can be set as defaults by simply calling default, as is done for :from at the top of this class. Feel free to tailor this to your needs.

The comments in this class also indicate that subject lines are already enabled for translation, a subject we’ll cover in Chapter 16, Task K: Internationalization. For now, we’ll simply use the :subject parameter.

As with controllers, templates contain the text to be sent, and controllers and mailers can provide values to be inserted into those templates via instance variables.

Email Templates

The generate script created two email templates in app/views/order_mailer, one for each action in the OrderMailer class. These are regular erb files. We’ll use them to create plain-text emails (you’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 received.text.erb; this is the email that’s sent to confirm an order:

 Dear ​<%=​ @order.​name​ ​%>
 
 Thank you for your recent order from The Pragmatic Store.
 
 You ordered the following items:
 
 <%=​ render @order.​line_items​ ​-%>
 
 We'll send you a separate email when your order ships.

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:

 <%=​ sprintf(​"%2d x %s"​,
  line_item.​quantity​,
  truncate(line_item.​product​.​title​, ​length: ​50)) ​%>

We now have to go back and fill in the received method in the OrderMailer class:

 def​ ​received​(order)
  @order = order
 
  mail ​to: ​order.​email​, ​subject: ​​'Pragmatic Store Order Confirmation'
 end

What we did here is add order as an argument to the method-received call, add code to copy the parameter passed into an instance variable, and update the call to mail specifying where to send the email and what subject line to use.

Generating Emails

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. Note that just calling the method we defined isn’t enough; we also need to tell Rails to actually send the email. The reason this doesn’t happen automatically is that Rails can’t be 100 percent sure if you want to deliver the email right this moment, while the user waits, or later, in a background job.

Generally, you don’t want the user to have to wait for emails to get sent, because this can take a while. Instead, we’ll send it in a background job (which we’ll learn more about later in the chapter) by calling deliver_later (to send the email right now, you’d use deliver_now.[72])

 def​ ​create
  @order = Order.​new​(order_params)
  @order.​add_line_items_from_cart​(@cart)
 
  respond_to ​do​ |format|
 if​ @order.​save
  Cart.​destroy​(session[​:cart_id​])
  session[​:cart_id​] = ​nil
» OrderMailer.​received​(@order).​deliver_later
  format.​html​ { redirect_to store_index_url, ​notice:
  ​​'Thank you for your order.'​ }
  format.​json​ { render ​:show​, ​status: :created​,
 location: ​@order }
 else
  format.​html​ { render ​:new​ }
  format.​json​ { render ​json: ​@order.​errors​,
 status: :unprocessable_entity​ }
 end
 end
 end

And we need to update shipped as we did for received:

 def​ ​shipped​(order)
  @order = order
 
  mail ​to: ​order.​email​, ​subject: ​​'Pragmatic Store Order Shipped'
 end

Now, we have enough of the basics in place that you can place an order and have a plain email sent to yourself, assuming you didn’t disable the sending of email in development mode. Let’s spice up the email with a bit of formatting.

Delivering Multiple Content Types

Some people prefer to receive email in plain-text format, while others like the look of an HTML email. Rails supports this directly, allowing you to send email messages that contain alternative content formats, allowing users (or their email clients) to decide which they’d prefer to view.

In the preceding section, we created a plain-text email. The view file for our received action was called received.text.erb. This is the standard Rails naming convention. We can also create HTML-formatted emails.

Let’s try this with the order-shipped notification. We don’t need to modify any code—we simply need to create a new template:

 <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 @order.​line_items​ ​-%>
 </table>

We don’t need to modify the partial, because the existing one will do just fine:

 <%​ ​if​ line_item == @current_item ​%>
 <tr class=​"line-item-highlight"​>
 <%​ ​else​ ​%>
 <tr>
 <%​ ​end​ ​%>
  <td class=​"quantity"​>​<%=​ line_item.​quantity​ ​%>​</td>
  <td>​<%=​ line_item.​product​.​title​ ​%>​</td>
  <td class=​"price"​>​<%=​ number_to_currency(line_item.​total_price​) ​%>​</td>
 </tr>

But for email templates, Rails provides a bit more naming magic. If you create multiple templates with the same name but with different content types embedded in their filenames, Rails will send all of them in one email, arranging the content so that the email client can distinguish each.

This means you’ll want to either update or delete the plain-text template that Rails provided for the shipped notifier.

Testing Email

When we used the generate script to create our order mailer, it automatically constructed a corresponding order_test.rb file in the application’s test/mailers directory. It’s pretty straightforward; it simply calls each action and verifies selected portions of the email produced. Because we’ve tailored the email, let’s update the test case to match:

 require ​'test_helper'
 
 class​ OrderMailerTest < ActionMailer::TestCase
  test ​"received"​ ​do
» mail = OrderMailer.​received​(orders(​:one​))
» assert_equal ​"Pragmatic Store Order Confirmation"​, mail.​subject
» assert_equal [​"[email protected]"​], mail.​to
» assert_equal [​"[email protected]"​], mail.​from
» assert_match ​/1 x Programming Ruby 1.9/​, mail.​body​.​encoded
 end
 
  test ​"shipped"​ ​do
» mail = OrderMailer.​shipped​(orders(​:one​))
» assert_equal ​"Pragmatic Store Order Shipped"​, mail.​subject
» assert_equal [​"[email protected]"​], mail.​to
» assert_equal [​"[email protected]"​], mail.​from
» assert_match ​/<td[^>]*>1</td>s*<td>Programming Ruby 1.9</td>/​,
» mail.​body​.​encoded
 end
 
 end

The test method instructs the mail class to create (but not to send) an email, 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. Your results may differ depending on how you tailored the default :from line in your OrderMailer.

Note that it’s also possible to have your Rails application receive emails. We’ll cover that in Chapter 17, Task L: Receive Emails and Respond with Rich Text.

Now that we’ve implemented our mailer and tested it, let’s move on to that pesky slow payment processor. To deal with that, we’ll put our API calls into a job that can be run in the background so the user doesn’t have to wait.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.122.162