How to configure Actionmailer

how to test actionmailer attachment and how to debug actionmailer and how to use actionmailer rails and how to send action mailer
Dr.MohitBansal Profile Pic
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.