how to test actionmailer attachment and how to debug actionmailer and how to use actionmailer rails and how to send action mailer
Dr.MohitBansal,Canada,Teacher
Published Date:26-10-2017
Your Website URL(Optional)
Comment
■ ■ ■
Sending E-mail and Building
a Newsletter Mailing List
In this chapter, we will add functionality to allow the RailsCoders site to send e-mail to our
users. There are many instances where it is useful to be able to automatically send e-mails
directly to users, such as sending a welcome mail when they sign up or mail to allow them to
reset their passwords. In this chapter, we will create an automated mailer that will inform users
when someone has left a new comment in their blogs, which will allow the blog’s owner to
quickly reply to the comment.
We will also build an e-mail newsletter feature. Instead of a being an automated mailer,
this feature will allow the administrator of the site to easily create and send newsletters or
notices to all users of the site.
Using ActionMailer
The Rails framework includes a module called ActionMailer. As you will guess by its name, it is
designed to allow you to easily send and receive e-mails from a Rails application. It can send
mails using either a Simple Mail Transfer Protocol (SMTP) server or a local sendmail applica-
tion. ActionMailer uses ERb templates in the same way as the web templates we have used to
build the web site.
To send e-mails from Rails, you need to create a type of model called a mailer. Mailers are
special types of models that inherit from theActionMailer::Base class. You can create methods
within this model that are used to set items such as the recipient and the subject, along with
variables in the e-mail templates that you have created. You can also add attachments or create
multipart e-mails. These model files are placed in the usual model directory,app/models/.
The e-mail templates for the mailer methods you create are placed in the views directory
corresponding to the model name. For example, if your mailer model is callednotifier, the
e-mail templates for this are placed in the directoryapp/views/notifier/.
Configuring ActionMailer
Before you send e-mails using ActionMailer, you will have to configure your application to
work with either an SMTP server or a local sendmail application. This is done in the Rails configu-
ration files in theconfig/ directory.
217 218 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
We have talked before about the fact that Rails has three modes for running an application:
development, test, and production. Since it is likely that the mail server settings will be different
for an application running on your local development machine or remotely on a server, you
can specify different ActionMailer configurations for each of the Rails run modes.
Within theconfig/ directory, there is another directory calledenvironments/. This has
three configuration files, one for each mode:development.rb,test.rb, andproduction.rb.
The test mode is already set up not to actually deliver mails but to collect them in an array
that is accessible by the test methods.
If you look at the config/environments/test.rb file, you will see that this is set by the
following line:
config.action_mailer.delivery_method = :test
If you are using Linux or OS X, you may wish to use the sendmail application to send e-mails
from your local machine. This requires very little configuration and is convenient for develop-
ment mode.
To configure ActionMailer to use sendmail, edit the relevant configuration file, for example,
config/environments/development.rb, by adding the following line:
config.action_mailer.delivery_method = :sendmail
For most purposes, configuring ActionMailer to use an SMTP sever is the best method.
However, you will need to know the settings of your SMTP server. Most ISPs and hosting
companies provide you with an SMTP server.
To configure your application to use SMTP, add the following line to the relevant environ-
ment configuration file, such asconfig/environments/development.rb:
config.action_mailer.delivery_method = :smtp
You also need to provide the details of the SMTP server. This is done in theconfig/
environment.rb file. At the end of this file, add the following code, and replace theaddress,
user_name, andpassword with your own settings:
ActionMailer::Base.smtp_settings =
:address = 'smtp.yourserver.com', default: localhost
:port = '25', default: 25
:authentication = :plain, :plain, :login or :cram_md5
:user_name = 'user',
:password = 'pass'
Specifying the E-mail Feature Requirements
For many aspects of our application, sending e-mails to our users would be very useful, such as
sending a welcome e-mail when they sign up or allowing them to receive recent news articles
as e-mails. C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 219
In this chapter, we will add a facility so that new comments left on users’ blogs are auto-
matically sent to them as e-mails. Also, we want to be able to send an occasional newsletter to
all users of the site.
E-mail Notifications of New Comments
When a new comment is left in response to a post on a user’s blog, we want to notify the owner
of the blog that someone has commented and provide a link to allow the user to easily see the
comment. We can do this by adding code to the specific action where this event happens, that
is, the comments controller’screate action.
ActionMailer allows us to create e-mails based on an e-mail template, so we can write a
generic e-mail that will be personalized for each outgoing e-mail with the user and comment
details.
We want the mail to have both plain text and HTML parts, meaning that the mail will display
as only plain text on text-based e-mail applications and as an HTML e-mail in applications that
support it.
The e-mail should contain a link to the entry that has been commented on, allowing the
user to quickly go to the entry and respond.
E-mail Newsletters
We want to be able to send a message to all registered users of the site. This could be used for
newsletters or to notify users of upgrades or new features.
To respect your users’ privacy, you should consider adding a user-settable option for whether
they should receive bulk e-mails or not. In this chapter, we will not implement this, but it should be
considered for a live site.
Sending an e-mail message to many users at once can put a strain on your server if you are
running a local sendmail process. SMTP servers are generally limited to accept only a limited
number of messages within a given time frame. This may be as little as a hundred e-mails per
hour, depending on your SMTP server provider. Because we want to send a personalized e-mail
message to each user, we cannot just specify a list of recipients on a BCC list.
Therefore, we need to work out a way of sending a large number of e-mails without over-
loading our system. Unsurprisingly, this problem has already been solved and built into a plug-in
calledar_mailer.ar_mailer was developed by Eric Hodel, and you can find the documentation
at http://dev.robotcoop.com/Tools/ar_mailer.
ar_mailer utilizes a database table to store all the to-be-sent e-mails, which are subsequently
processed by a separate script,ar_sendmail. When thear_sendmail script is executed, it will
process each of the messages waiting to be sent in the database table, sending them with the
SMTP settings set up for ActionMailer.
In order to allow newsletters to be created, stored, and edited before sending, we will also
create a resource for the newsletters that we send to users. This model simply needs to store the
e-mail subject, body text, and the time and date that it was created. The model should also
store whether the newsletter has been sent or not and if so, the date and time that it was sent.
This database structure is shown in Table 8-1. 220 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
Table 8-1. The Newsletter Database Structure
Field Name Field Type Description
id integer The primary key
subject string The e-mail subject line
body text The body of the e-mail
sent boolean Whether this newsletter has been sent or not
created_at datetime The date and time that the newsletter was created
Along with the normal REST actions, the controller for the newsletter resource also needs
to have an action that actually sends the newsletter out to the users. We will call this action
sendmails.
■Caution Be careful when naming your actions, as you cannot use certain method names that are
reserved by the system. For instance, you cannot use the namesend for an action in a Rails controller.
Building the New Comment Notifier
To allow us to send e-mails on a specific event, we will have to create a Rails mailer together
with the relevant views for this mailer.
We also need to call this mailer when a specific event occurs. We will do this from an
existing controller.
Creating the Mailer
To create a Mailer model, we will use the Railsgenerate script. This will create a skeleton model,
views directory, and test code.
Enter the following command:
ruby script/generate mailer Notifier
exists app/models/
create app/views/ notifier
exists test/unit/
create test/fixtures/ notifier
create app/models/notifier.rb
create test/unit/notifier_test.rb
If you open the Mailer model file,app/models/notifier.rb, you will notice that this class
definition inherits from theActionMailer class. Now, edit this file as shown in Listing 8-1. C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 221
Listing 8-1. The Notifier Mailer Model
class Notifier ActionMailer::Base
def new_comment_notification(comment)
blog_owner = comment.entry.user
recipients blog_owner.email_with_username
from "RailsCoders systemrailscoders.net"
subject "A new comment has been left on your blog"
body :comment = comment,
:blog_owner = blog_owner,
:blog_owner_url = "http://railscoders.net/users/blog_owner.id",
:blog_entry_url =
"http://railscoders.net/users/blog_owner.id/entries/comment.entry.id"
end
end
You will notice that thenew_comment_notification method specifies the e-mail address of
recipients, the e-mail address that the mail is to be sent from, and a subject for the message,
and it passes an instance variablecomment to the template. You can specify any number of variables
here, which are all passed to the template and can be used in your e-mail.
For the recipient’s e-mail address, we have usedemail_with_username. However, at the
moment, no such attribute exists for the model. When specifying an e-mail address, you can
use a friendly name and specify the e-mail address within and, as we did with the from address.
To produce an e-mail address for a user, we will add a method calledemail_with_username to
the User model.
To do this, open the User model file,app/models/user.rb. Add the following method near
to the end of the file but within theUser class:
...
def email_with_username
"username email"
end
end
This will now return the user’s username and e-mail in the desired format.
In thenew_comment_notification method, you will notice that we create an instance vari-
able calledblog_entry_url in the variables that are passed to the template. This is simply a
string consisting of the URL of the entry. Notice here that we have to manually specify the site’s
hostname,railscoders.net, because when a mailer is executed, it has no knowledge about the
request. A normal action method responds to an HTTP request, specifying the URL of the site
along with the request. However, when a mailer is called from a controller, it does not have any
context about the incoming request, so we have to specify the details ourselves.
We now need to create the view template that corresponds to the mailer method
new_comment_notification. Create the fileapp/views/notifier/
new_comment_notification.rhtml, and enter the e-mail template shown in Listing 8-2. 222 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
Listing 8-2. The Comment Notification E-mail Template
Hi %= blog_owner.username %,
A new comment has been left on your blog at RailsCoders.net.
The comment was left by '%= comment.user.username %' at ➥
%= comment.created_at.to_s(:short) %.
To read the comment, go to %= blog_entry_url %.
Cheers,
The RailsCoders Team
As you can see, this uses the instance variables set by thenew_comment_notification
method within the e-mail.
However, this e-mail seems a little old school, being only a plain text e-mail. Since there is
a link to the blog entry with the new comment supplied in the e-mail, it would be nice to send
the e-mail as an HTML e-mail.
Thankfully, ActionMailer allows us to easily create multipart e-mails, delivering an e-mail
with a plain text part and an HTML part. E-mail readers are smart enough to know which parts
they should display, depending on their configuration.
To do this, all we have to do is provide templates with certain names. Rails will automatically
look for template names for the particular method with a content type specified before the.rhtml
suffix. Therefore, files ending in.text.plain.rhtml,.text.html.rhtml, or.text.xml.rhtml will be
rendered and attached to the e-mail as separate parts with the appropriate content type. A file
ending in.text.html.rhtml will be sent with the content type oftext/html.
Since the e-mail template we have already written is in plain text, rename that asnew_comment_
notification.text.plain.rhtml, keeping it within the same directory.
We can now create a HTML version of the same e-mail. Create the fileapp/views/notifier/
new_comment_notification.text.html.rhtml, and enter the view shown in Listing 8-3.
Listing 8-3. The HTML New Comment Notification E-mail Template
%= image_tag "http://railscoders.net/images/logo.png", :alt = "RailsCoders" %
pHi %= blog_owner.username %,/p
pA new comment has been left on your blog at RailsCoders./p
pThe comment was left by %= link_to comment.user.username,
blog_owner_url % at %= comment.created_at.to_s(:short) %./p
pTo read the comment, go to %= link_to blog_entry_url, blog_entry_url %/p
pCheers,br /
a href="http://railscoders.net"The RailsCoders Team/a/p C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 223
Now we have made the e-mail a little more interesting by adding links to the comment
author’s profile and the entry for which the comment was made, along with including our logo.
Manually Testing E-mail Creation
Before we integrate the comment notification feature into our system, we can test it out manu-
ally, to check that the expected mail is composed by our notifier and templates. To do this, we
are going to use the interactive features of Ruby, specifically the Rails console.
The Rails console allows you to interactively use your application using Ruby and Rails
commands. This allows you to investigate inside your system rather than just using the web
interface and looking at logs.
To start the Rails console, open a terminal window, and enter the following command:
ruby script/console
Loading development environment.
The is a prompt for you to enter commands. Here, you can now run any Ruby or Rails
commands and immediately see the result. You can also inspect and debug your code.
For instance, if you wanted to check that the newemail_with_username method that we
added to the User model earlier is working, try the following:
adminuser = User.find_by_username('admin')
User:0x33ea584 attributes="last_login_at"=nil, "updated_at"="2007-01-10
05:55:00", "profile"="Site Administrator", "hashed_password"="8c6976e5b5410
415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", "entries_count"="0",
"username"="Admin", "enable_comments"=nil, "blog_title"=nil, "enabled"="1",
"id"="1", "posts_count"="3", "created_at"="2006-12-11 23:52:23",
"email"="adminexample.com"
You can see from the result that Rails has executed the code that we entered. You can now
use this instance of the User model,adminuser, as you would in your Rails code. For instance,
to test theemail_with_username method, enter the following:
adminuser.email_with_username
"Admin adminrailscoders.net"
This is exactly what we expected and wanted—the username together with the e-mail
address enclosed in and.
We can now use this same method to manually test the e-mail creation. It is also a good
idea to use this in conjunction with monitoring the Rails log files. When you enter a command, 224 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
the log files will show any SQL queries that are executed and also the content of any e-mails
that are generated by the system.
■Tip By default, Rails will attempt to deliver e-mails created in the development mode. If you wish to
change this, add config.action_mailer.delivery_method = :test to your development mode
configuration file,config/environments/development.rb.
To invoke a method in a mailer class, you simply prefixdeliver_ to the method name
and call that as a class method of the mailer class. For example, to invoke thenew_comment_
notification method and deliver the e-mail, you would useNotifier.deliver_new_comment_
notification(comment), wherecomment is the comment object of the new comment that has
been left on the blog.
We can try this now. First, make sure that you have a blog entry added for one of your users
and that this blog entry has a comment added to it. For this example, I have an entry withid of
1, which has a comment with the comment id of1.
To create an instance of this comment object, use thefind command as follows:
comment = Comment.find(1)
Comment:0x3403430 attributes="updated_at"="2007-01-03 16:55:26",
"entry_id"="1", "body"="a comment", "id"="1", "user_id"="2",
"created_at"="2007-01-03 16:55:26"
As you can see from the console output, we have successfully retrieved thecomment object.
If you look at thedevelopment.log file, you will see the SQL query that was performed:
Comment Columns (0.135616) SHOW FIELDS FROM comments
Comment Load (0.043175) SELECT FROM comments WHERE (comments.id = 1)
You can now invoke thedeliver_new_comment_notification method, creating and sending
the e-mail to the blog owner:
mail = Notifier.deliver_new_comment_notification(comment)
TMail::Mail port=TMail::StringPort:id=0x19db598
bodyport=TMail::StringPort:id=0x19da030
If you take a look at thedevelopment.log file now, you will see the entire e-mail, complete
with the plain text and HTML parts: C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 225
Sent mail:
From: systemrailscoders.net
To: Alan abradburnegmail.com
Subject: A new comment has been left on your blog
Mime-Version: 1.0
Content-Type: multipart/alternative; boundary=mimepart_45bfd0d45013b_18d7118ba0152
mimepart_45bfd0d45013b_18d7118ba0152
Content-Type: plain/text; charset=utf-8
Content-Transfer-Encoding: Quoted-printable
Content-Disposition: inline
Hi Alan,
A new comment has been left on your blog at RailsCoders.net.
The comment was left by 'Alan' at 03 Jan 16:55.
To read the comment, go to http://railscoders.net/users/2/entries/1
mimepart_45bfd0d45013b_18d7118ba0152
Content-Type: plain/html; charset=utf-8
Content-Transfer-Encoding: Quoted-printable
Content-Disposition: inline
img alt="RailsCoders" src="http://railscoders.net/images/logo.png" /
pHi Alan,/p
pA new comment has been left on your blog at RailsCoders./p
pThe comment was left by a href="http://railscoders.net/users/2"Alan/a on
03 Jan 16:55.
To read the comment, go to a href="http://railscoders.net/users/2/entries/1"
http://railscoders.net/users/2/entries/1/a
Cheers,
pa href="http://railscoders.net"The RailsCoders Team/a/p
mimepart_45bfd0d45013b_18d7118ba0152
This has created a new object calledmail. You can also interrogate themail object that you
just created in the Rails console. 226 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
■Tip The Rails mailer uses a Ruby library called TMail to work with e-mails. You can find the TMail docu-
mentation at http://i.loveruby.net/en/projects/tmail/doc.
For example, to look at the addressee of the e-mail, type the following command:
mail.to
= "abradburnegmail.com"
Or to view the entire e-mail header, use this:
mail.header
"message-id"=TMail::MessageIdHeader "45c1c6b631951_18d7118ba045alans-
computer.local.tmail", "mime-version"=TMail::MimeVersionHeader "1.0",
"from"=TMail::AddressHeader "systemrailscoders.net", "content-
type"=TMail::ContentTypeHeader "multipart/alternative",
"date"=TMail::DateTimeHeader "Thu, 04 Jan 2007 10:53:42 +0000",
"subject"=TMail::UnstructuredHeader "A new comment has been left on your blog",
"to"=TMail::AddressHeader "\"Alan\" abradburnegmail.com"
To display the body of the text-only part of the e-mail, use this:
mail.parts.first.body
= "Hi Alan,\n\nA new comment has been left on your blog at RailsCoders.net.\n\n
The comment was left by 'Alan' at 03 Jan 16:55.\n\nTo read the comment, go to
http://railscoders.net/users/2/entries/1"
Now that we can see that the e-mail created by our notifier method looks correct, we can
integrate it with the necessary controller.
Calling the Mailer from the Comments Controller
This e-mail will be sent when a new comment is added to a blog. As you will remember from
Chapter 6, new comments are created by thecreate action of the comments controller.
Open the comments controller, app/controllers/comments_controller.rb, and take a
look at thecreate action. For reference, this is shown in Listing 8-4. C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 227
Listing 8-4. The Comments Controller Create Action
def create
entry = Entry.find_by_user_id_and_id(params:user_id,
params:entry_id)
comment = Comment.new(:user_id = logged_in_user.id,
:body = params:comment:body)
if entry.comments comment
flash:notice = 'Comment was successfully created.'
redirect_to entry_path(:user_id = entry.user,
:id = entry)
else
render :controller = 'entries', :action = 'show',
:user_id = entry.user, :entry_id = entry
end
end
We need to invoke thedeliver_new_comment_notification method when we know that a
comment has been successfully created and associated with an entry. Therefore, we need to
add the invocation within the when-true execution path of theif statement, where the comment
has successfully been saved and added to an entry.
Add the mailer delivery method as follows:
...
if entry.comments comment
flash:notice = 'Comment was successfully created.'
Notifier.deliver_new_comment_notification(comment)
redirect_to entry_path(:user_id = entry.user, :id = entry)
else
...
We can now try running this from within the application.
Testing the Mailer from Within the Application
Make sure that the Rails application server is running and that you are logged into the site as a
regular user. Now, visit a blog on the site that already has a blog entry, and click the comments
link beneath the entry to create a new comment.
You should watch the development log file, using either the commandtail –f log/
development.log if you are using OS X or Linux or a tail application if you are using Windows.
Create a new comment for this entry, and click save. The comment will be created, and
your browser will be directed to the blog index view.
Take a look at your development log file. Scroll back to the beginning of the processing for
this request, marked by the following line: 228 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
Processing CommentsControllercreate (for 127.0.0.1 at 2007-01-04 12:42:26) POST
Beneath this, you will see all of the SQL queries that were performed for this request,
including theINSERT statement used to create the comment and theUPDATE statement that
associates the comment with the entry.
After these, you will see the mail that was created and sent to the owner of the blog entry.
Automating the Mailer Tests
Of course, while manually testing the mailer is useful, we should write automated test cases
too. We should develop both unit tests and functional tests.
The unit test cases will test the mailer on its own. We will use the fixtures that we have
already created to make a new comment notification e-mail, which we will then test using
some simple assertions that look for specific patterns in the created mail, such as the correct
URL to the entry and the correct username of the comment poster.
The functional test cases will test that the right e-mail is sent at the right time. In our appli-
cation, the new comment notification e-mail will be sent when a new comment is saved, so this
is what we need to check.
Unit Tests
When you used thegenerate command to create a mailer, Rails also creates the skeleton file for
your mailer. This is placed with the other unit tests in thetest/unit/ directory. Open thetest/
unit/notifier.rb file now.
You will notice that this is similar to a normal unit test file, except that along with setting a
few variables and including anActionMailer class, asetup method and two private methods,
read_fixture andencode, are provided.
Our unit test should simply retrieve one of our comment fixtures and create a new notifi-
cation message based on that. We can then test that the dynamic text within that e-mail is
correct by performing several assertions.
To add the test, edit thenotifier.rb file as shown in Listing 8-5.
Listing 8-5. Unit Test for the Comment Notification
require File.dirname(__FILE__) + '/../test_helper'
class NotifierTest Test::Unit::TestCase
FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures'
CHARSET = "utf-8"
fixtures :entries, :comments, :users
include ActionMailer::Quoting C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 229
def setup
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries =
expected = TMail::Mail.new
expected.set_content_type "text", "plain", "charset" = CHARSET
expected.mime_version = '1.0'
end
def test_comment_notify
comment = Comment.find(1)
response = Notifier.create_new_comment_notification(comment)
assert_equal "A new comment has been left on your blog", response.subject
assert_match /Hi comment.entry.user.username/, response.body
assert_match /The comment was left by 'comment.user.username' at ➥
comment.created_at.to_s(:short)/, response.body
assert_match /go to http:\/\/railscoders.net\/users\/1\/entries\/1/,
response.body
end
private
def read_fixture(action)
IO.readlines("FIXTURES_PATH/notifier/action")
end
def encode(subject)
quoted_printable(subject, CHARSET)
end
end
You should now run this unit test as follows:
ruby test/unit/notifier_test.rb
Loaded suite test/unit/notifier_test
Started
.
Finished in 0.253968 seconds.
1 tests, 4 assertions, 0 failures, 0 errors
Functional Tests
To ensure that mail is sent at the right time, we should write functional tests to initiate sending
a comment notification e-mail. 230 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
Since this is handled by the comments controller, we will add this functional test
to the comments functional test file that we already created. Opentest/functional/
comments_controller_test.rb now. Add the following test to the end of this file, before the last
end statement:
def test_send_notify_email
num_deliveries = ActionMailer::Base.deliveries.size
login_as(:valid_user)
post :create,:user_id = 1, :entry_id = 1,
:comment = :body = 'that is great'
assert_equal num_deliveries + 1, ActionMailer::Base.deliveries.size
end
This test simply checks the number of e-mails to be delivered before and after a comment
is created. After a new comment is created, there should be one extra message.
Run the comments controller functional tests again to check that this test passes:
ruby test/functional/comments_controller_test.rb
Loaded suite test/functional/comments_controller_test
Started
....
Finished in 0.313221 seconds.
4 tests, 9 assertions, 0 failures, 0 errors
Building the Newsletter Feature
The newsletter feature is a little more complex than the comment notification. First of all, we
need some way of storing the newsletter. Since a newsletter is going to be different every time,
we need a little more than just an ERb mail template.
Installing ar_mailer
As discussed earlier, we are going to use the Ruby gemar_mailer to make it easier for us to send
mail to a large number of users at the same time.
If we decide to usear_mailer, we need to change the delivery method for the system.
Rather than using the SMTP or sendmail settings shown at the beginning of the chapter, we tell
ActionMailer to use ActiveRecord as the delivery method.
First of all, install the gem using the following command: C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 231
gem install ar_mailer
Successfully installed ar_mailer-1.1.0
Installing ri documentation for ar_mailer-1.1.0...
Installing RDoc documentation for ar_mailer-1.1.0...
■Note On Linux or OS X, you may need to prefix thegem install command with sudo.
Thear_mailer gem utilizes a database table to store all of the messages that it needs to
send. We need to create a migration to add the necessary table to our database.
Create a new migration with the following command:
ruby script/generate model Email
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/email.rb
create test/unit/email_test.rb
create test/fixtures/emails.yml
exists db/migrate
create db/migrate/017_create_emails.rb
Edit the migration filedb/migrate/017_create_emails.rb as shown in Listing 8-6. This will
create a new table calledemails to store the e-mails waiting to be sent.
Listing 8-6. The ar_mailer Migration Script
class CreateEmails ActiveRecord::Migration
def self.up
create_table :emails do t
t.column :from, :string
t.column :to, :string
t.column :last_send_attempt, :integer, :default = 0
t.column :mail, :text
end
end
def self.down
drop_table :emails
end
end 232 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
To use thear_mailer code to deliver e-mails, we need to change theNotifier class to
inherit fromActionMailer::ARMailer instead ofActionMailer::Base. Open the notifier,app/
models/notifier.rb, and change the class definition line as follows:
class Notifier ActionMailer::ARMailer
def new_comment_notification(comment)
blog_owner = comment.entry.user
...
We also need to change the delivery method in the Rails configuration files to deliver e-mails
using ActiveRecord. We will change the delivery method for the development mode, since we
are only working in development mode here. You should also make the same changes in the
production.rb file if you are going to usear_mailer in a production environment.
Open the Rails configuration file,config/environments/development.rb, and change the
delivery method as follows:
config.action_mailer.delivery_method = :activerecord
Finally, we need to make sure that the correct paths are set for thear_mailer to be loaded
by Rails. Edit the fileconfig/environment.rb by adding the following line to the end of the file:
require 'action_mailer/ar_mailer'
Creating the Skeleton Resource
To allow easy creation and editing of the newsletters, we will create a new resource, called
newsletters, which is only accessible by the Admin user.
We will use thescaffold_resource generator to create the resource and modify the generated
code to meet our requirements. We can add the attribute names and their database table types
to the command, and they will be automatically added to the migration script:
ruby script/generate scaffold_resource Newsletter
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/newsletters
exists test/functional/
exists test/unit/
create app/views/newsletters/index.rhtml
create app/views/newsletters/show.rhtml
create app/views/newsletters/new.rhtml
create app/views/newsletters/edit.rhtml
create app/views/layouts/newsletters.rhtml
identical public/stylesheets/scaffold.css
create app/models/newsletters.rb
create app/controllers/newsletters_controller.rb
create test/functional/newsletters_controller_test.rb
create app/helpers/newsletters_helper.rb C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 233
create test/unit/newsletters_test.rb
create test/fixtures/newsletters.yml
exists db/migrate
create db/migrate/018_create_newsletters.rb
route map.resources :newsletters
We can now update the migration file to add the database columns detailed in the specifica-
tion. Alter the migration file,db/migrations/018_create_newsletters.rb, as shown in Listing 8-7.
Listing 8-7. The Migration File for the Newsletter
class CreateNewsletters ActiveRecord::Migration
def self.up
create_table :newsletters do t
t.column :subject, :string
t.column :body, :text
t.column :sent, :boolean, :null = false, :default = false
t.column :created_at, :datetime
t.column :updated_at, :datetime
end
end
def self.down
drop_table :newsletters
end
end
Now, run the migration:
rake db:migrate
(in /Users/alan/Documents/Projects/Rails/railscoders)
== AddArMailerTable: migrating ================================================
create_table(:emails)
- 0.1708s
== AddArMailerTable: migrated (0.1709s) =======================================
== CreateNewsletters: migrating ===============================================
create_table(:newsletters)
- 0.0045s
== CreateNewsletters: migrated (0.0047s) ======================================
Mapping the Newsletter Resource
The newsletter resource is not nested under any other resources, so a simple top-level mapping,
as created by thegenerate script, is fine. 234 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
However, we have specified that we need an extra action for the newsletter resource,
calledsendmails. This will initiate the sending of a specific newsletter to all users. We need to
declare this in theroutes.rb file.
Since we want this action to be available only to members of the newsletter resource, we
declare it with the parameter:member. If we wanted to declare an action that was available to a
collection of newsletter resources as a whole, we would use the parameter:collection.
Open theconfig/routes.rb file now. You will see that thegenerate script has already created
the linemap.resources :newsletters. Modify this to include thesendmails action as follows:
map.resources :newsletters, :member = :sendmails = :put
This specifies that thesendmails action can only be accessed via an HTTPPUT request. This
will ensure that the send action is not accidentally initiated by a web accelerator application.
The Newsletter Model
The Newsletter model needs to have the validations added to it. Since the newsletters are not
related to any other models, there are no relationships to define.
Because the Newsletter model is very simple, the only two fields that are entered by a user,
and therefore, the only two that require validating are the message subject and body. We should
also use thevalidates_presence_of validation to test that neither field is left blank.
Open the newsletter model file,app/models/newsletter.rb, and add the validations shown
in Listing 8-8.
Listing 8-8. The Newsletter Model
class Newsletter ActiveRecord::Base
validates_presence_of :subject, :body
validates_length_of :subject, :maximum = 255
validates_length_of :body, :maximum = 10000
end
Writing the Newsletter Controller and Views
The newsletter controller is a simple resource, allowing an administrator user to create and
edit newsletters, which can then be sent by clicking a button on the newsletter show screen.
The scaffolding code produced by the generator gives us a useful starting point, but we
need to adapt it to our application. For simplicity, we will just implement a web version of the
controller. If you wish to allow this to be accessed via an XML API, you can add the functionality as
we have in earlier chapters.
Open the controller,app/controllers/newsletters_controller.rb, and edit it as shown in
Listing 8-9. C HA P TE R 8 ■ S E N D I N G E - M A I L AN D B U I L D IN G A N E W SL E T TE R M AI L I N G L I ST 235
Listing 8-9. The Newsletters Controller
class NewslettersController ApplicationController
before_filter :check_administrator_role
GET /newsletters
def index
newsletters = Newsletter.find(:all)
end
GET /newsletters/1
def show
newsletter = Newsletter.find(params:id)
end
GET /newsletters/new
def new
newsletter = Newsletter.new
end
GET /newsletters/1;edit
def edit
newsletter = Newsletter.find_by_id_and_sent(params:id, false)
end
POST /newsletters
def create
newsletter = Newsletter.new(params:newsletter)
if newsletter.save
flash:notice = 'Newsletter was successfully created.'
redirect_to newsletter_path(newsletter)
else
render :action = "new"
end
end
PUT /newsletters/1
def update
newsletter = Newsletter.find_by_id_and_sent(params:id, false)
if newsletter.update_attributes(params:newsletter)
flash:notice = 'Newsletter was successfully updated.'
redirect_to newsletter_path(newsletter)
else
render :action = "edit"
end
end 236 CH AP T E R 8 ■ SE N D IN G E - M AI L A N D B U IL D I N G A N E W S L E TT E R M AI LI N G L IS T
DELETE /newsletters/1
def destroy
newsletter = Newsletter.find_by_id_and_sent(params:id, false)
newsletter.destroy
redirect_to newsletters_path
end
PUT /newsletters/1;send
def sendmails
newsletter = Newsletter.find_by_id_and_sent(params:id, false)
users = User.find(:all)
users.each do user
Notifier.deliver_newsletter(user, newsletter)
end
newsletter.update_attribute('sent', true)
redirect_to newsletters_path
end
end
We need to make sure that only administrator users can access this controller, so we will
protect it using the before filtercheck_administrator_role.
The Index Action and View
Theindex action method simply returns all newsletters. This is fine, but we have modified the
action method to order the newsletters by the last update time, showing the most recently
updated item first.
For the view, we want to show the list of newsletters including the subjects, whether or not
the newsletters have been sent, the times they were created and updated, along with links to
show, edit, or delete newsletters. However, since we do not want an administrator user to edit
an already sent newsletter, we should only show these links for newsletters that have not been
sent. Thesent Boolean field tells us if it has been sent or not. However, this will displaytrue or
false, whereas it would be more user friendly to display “yes” or “no.” To do this, we will create
a helper method to convert true to the string'yes' and false to the string'no'. Since this method
might be useful for other views, not just the newsletter views, we will create it in the applica-
tionwide helper file,app/helpers/application_helper.rb.
Open this file now, and edit it as shown in Listing 8-10.
Listing 8-10. The Applicationwide Helper File
module ApplicationHelper
def yes_no(bool)
if bool == true
"yes"
Advise:Why You Wasting Money in Costly SEO Tools, Use World's Best Free SEO Tool Ubersuggest.