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