Chapter 17. Mail in Rails

Rails is mostly a web application framework, but there are many connections that are better made through email. Rails includes a gem, ActionMailer, that lets your application send and receive email messages. Whether email is just something you use to confirm user accounts, or send notifications, or is at the heart of your application, ActionMailer is the key to connecting your Rails application to email.

Warning

ActionMailer is built into Rails, but it has its own way of doing things, dating back to much earlier iterations of Rails. It supports models with views but not controllers, for instance, making it difficult to see how method calls connect to results. The best way to deal with this—for now—is to accept that ActionMailer is a somewhat different part of Rails, and keep careful track of where it puts its pieces.

Sending Text Mail

Text-based email is a good foundation for sending messages from Rails. Some people simply prefer text email, but in any case it’s the simplest way to get going, minimizing the already fairly large set of pieces that need to be coordinated for Rails to send email messages. Awards typically come with certificates to be handed out, so sending an email will be a good way to show Rails’ mailing functionality.

Setup

ActionMailer is only one of the components you need to send email to and from Rails. Unfortunately, it’s the only part that this book can explain in depth, because every mail setup on every server has its structure and its own quirks. (This example is in ch17/student010, but be aware that it may need additional configuration to work with your server.) The key setup for sending mail takes place in the config/environments/development.rb file (and in test.rb and production.rb when you move into testing and production). Near the bottom of development.rb you can add a set of options, shown in Example 17-1.

Example 17-1. Options for sending mail, from development.rb

# you can normally use sendmail on Mac or Linux
config.action_mailer.delivery_method = :sendmail

# or setup to use your ISP's mail server: omit authentication
# details if they don't require them.

# config.action_mailer.delivery_method = :smtp
# config.action_mailer.server_settings = {
#   :address    => "smtp.myisp.net",
#   :port       => 25,
#   :domain     => "mydomain.example.org",
#   :authentication => :login,
#   :user_name  => "myusername",
#   :password   => "secret"
# }

If the sendmail command works on your computer—which doesn’t necessarily mean that the sendmail server is installed—you can leave config.action_mailer.delivery_method set to :sendmail. It’s certainly simpler than configuring the more detailed options for working with another mail server. If you need to connect to a distant mail server, it may be easiest to test that configuration in a mail client and then copy those details over to these settings. Be sure to comment out the line calling sendmail and uncomment the lines you need for your server’s configuration.

Adjusting Routing for an email Method

The next step toward sending email messages when students receive awards is to add an email method to the AwardsController class. Before doing that, though, it’s best to ensure that the method will get called by routing. Since AwardsController is a nested resource, as described in Chapter 9, there’s a bit of extra work to do. The original routing leading to the :awards controller looks like:

map.resources :students, :has_many => [ :awards ],
    :member => { :courses => :get, :course_add => :post, 
    :course_remove => :post}

There’s no place there to declare additional methods for :awards, so it’s necessary—as Chapter 15 noted—to change the form of the declaration. The first step is to convert :has_many to the more verbose equivalent form:

map.resources :students, :member => { :courses => :get,
    :course_add => :post,
    :course_remove => :post} do |student|
      student.resources :awards
    end

Now there’s a place to add an additional :member parameter for the email method, which will answer POST requests:

map.resources :students, :member => { :courses => :get,
    :course_add => :post,
    :course_remove => :post} do |student|
      student.resources :awards, :member => { :email => :post }
    end

Rails’ automatic routing capabilities become less automatic when developers add functionality beyond the defaults. It’s not hard to fix; it just requires being a bit more specific.

Sending Email

Actually sending an email message from a controller only takes one line of code, though that line of code is, of course, backed up by more code. The email method in app/controllers/awards_controller.rb, shown in Example 17-2, is reasonably simple.

Example 17-2. A controller method for sending email

def email
    @award = @student.awards.find(params[:id])
    AwardMailer.deliver_certificate(@award, current_user.email)
    flash[:notice] = "Email has been sent"
    redirect_to([@student, @award])
  end

That doesn’t look too bad. There’s an AwardMailer object somewhere with a deliver_certificate method on it, and it just needs the award object and an email address to deliver its certificate to. It sounds like a pretty typical request for a controller method—but there won’t be an award_mailer_controller.rb or anything like that in the app/controllers folder.

Instead, this call to action goes to a model, the AwardMailer object defined in app/models/award_mailer.rb. Remember, ActionMailer has its own way of structuring things, which doesn’t map to the expectations of the rest of Rails. The AwardMailer class you need to create to generate the mail message is pretty simple, though, as shown in Example 17-3.

Example 17-3. The AwardMailer model class, which seems to do very little

class AwardMailer < ActionMailer::Base

  def certificate(award, email)
    subject       award.name
    recipients    email
    from          'School System <[email protected]>'
    sent_on       Time.now
    body          :award => award
  end

end

As you can see, AwardMailer has a certificate method, but no deliver_certificate method. The certificate method doesn’t even seem to do anything—it just defines the contents of some fields that will be in the message. The last of them, body, is especially mysterious, setting the :award parameter to the award argument that came in, but not seeming to call anything.

The body information feeds directly into a view, in the file app/views/award_mailer/certificate.erb, without any controller moderating the model–view relationship. The certificate.erb file, shown in Example 17-4, is pretty plain, which is fine since it’s only generating a text-based email message.

Example 17-4. Generating a plain-text report of an award for emailing

<%= @award.name %>

awarded to

<%= @award.student.name %>

<%= @award.year %>

--
Courtesy of the School System!

Unlike every other view shown in this book so far, there aren’t any HTML tags here. There is an instance variable, @award, magically reconstituted by the :award => award value given for body in the model file. The end of the filename is just .erb, not .html.erb—when no second format is specified, text is the default.

Warning

If there is a certificate.html.erb file present, however, Rails will use that, leading to a text message with a lot of HTML tags in it. ActionMailer isn’t as smart about formats as the rest of Rails.

The last key component is a means of calling this method. For now, a button from the view that displays awards, app/views/awards/show.html.erb, is sufficient, as shown in Example 17-5.

Example 17-5. Connecting to the email method of the awards controller from a view

<p>
  <b>Name:</b>
  <%=h @award.name %>
</p>
<p>
  <b>Year:</b>
  <%=h @award.year %>
</p>
<p>
  <b>Student:</b>
  <%=h @award.student.name %>
</p>

<% form_tag email_student_award_path(@student, @award) do %>
  <%= submit_tag 'Email me this award' %>
<% end %>

<p>
<%= link_to 'Edit', edit_student_award_path(@student, @award) %> |
<%= link_to 'Back', student_awards_path(@student) %>
</p>

When the user visits an awards page, possibly after creating the award, they’ll see an “Email me this award” button, like that in Figure 17-1. Clicking that returns the web result shown in Figure 17-2 and the email shown in Figure 17-3.

An opportunity to send an email

Figure 17-1. An opportunity to send an email

A note that the email was sent—always a good idea to report in the interface, rather than waiting for the user to find the email

Figure 17-2. A note that the email was sent—always a good idea to report in the interface, rather than waiting for the user to find the email

The email, received

Figure 17-3. The email, received

The log also shows that a message was sent:

Sent mail to [email protected]

Date: Wed, 6 Aug 2008 21:14:22 -0400
From: School System <[email protected]>
To: [email protected]
Subject: Frogman award for underwater poise
Mime-Version: 1.0
Content-Type: text/plain; charset=utf-8

Frogman award for underwater poise

awarded to

Jules Miller

2008

--
Courtesy of the School System!
Redirected to http://localhost:3000/students/3/awards/3
Completed in 0.10415 (9 reqs/sec) | DB: 0.00112 (1%) | 302 Found
[http://localhost/students/3/awards/3/email]

ActionMailer has its quirks, but it does seem to work. Figure 17-4 shows the flow of data that generated this message.

Sending mail, from a controller to a model to a view to a message

Figure 17-4. Sending mail, from a controller to a model to a view to a message

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

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