18: Adding a Contact Form and Mailer




Learning Rails show

Summary: <h2>Goals</h2> <p>In this lesson, we’re creating the “Contact Us” page. There’s two major parts to this: creating the message model and the associated forms and admin setup, and then creating a mailer that takes new messages and sends them to the site administrator via email.</p> <p>Please note that, while we’ve tried to make these notes complete, they aren’t the full tutorial; that’s in the screencast, which you can access via the link on the left.</p> <h2>Setup</h2> <p>We begin with the code with which we ended Lesson 17. These zip files contain the beginning and ending states of the code:</p> <ul> <li><a href="/learningrails_17.zip">Learning Rails example app code as of the start of this lesson</a></li> <li><a href="/learningrails_18.zip">Learning Rails example app code as of the end of this lesson</a></li> </ul> <h2>Creating the contact form</h2> <h3>Scaffolding the Message Model</h3> <p>If we only wanted to send an email when the contact form was filled in, we wouldn’t really need to use an Active Record model and save it to the database. But it’s actually easier to use all the scaffolding and other support that Active Record provides, and it is handy to have the messages stored in the database so the can be reviewed independently of email.</p> <p>We start by creating a scaffold for contacts:</p> <pre> script/generate scaffold message name:string company:string phone:string email:string subject:string body:text </pre> <p>This generates the model, controller, views, and test files.</p> <p>Now run the migration:</p> <pre> rake db:migrate</pre> <p>And start the server:</p> <pre> script/server</pre> <h3>Hooking up the Contact Form</h3> <p>First delete the layout file the scaffold generates (views/layouts/messages.html.erb), so the scaffolded views will use our standard layout.</p> <p>We already have a contact button in the navigation bar, but this is pointing to one of our initial static pages, and now we want it to point to the new contact form. So run the sample app (script/server), log in, go to the Page Admin, and edit the Contact page to set it to redirect (using the capability we added in the <a href="/podcasts/79342-resources-page-links-categories-and-habtm">Lesson 17</a>) to the <code>new</code> action in the <code>messages</code> controller.</p> <p>Since we’ve hijacked a scaffolded form that was meant to be part of the admin and are using it for a user-facing form, we need to tweak it a bit. Delete this line from the end of views/messages/new.html.erb, since we don’t want visitors to try to get to the list of all messages:</p> <pre> &lt;%= link_to 'Back', messages_path %&gt; </pre> <h3>Redirecting to Home after Submission</h3> <p>We don’t want to redirect to the message/show action, which is part of the admin interface and is what the scaffolding does by default. So we’ll change the redirect to go to the home page, and change the flash message to something more appropriate. Users who submit a message will see the thank-you message at the top of the home page.</p> <p>In the message controller’s show action:</p> <pre> if @message.save flash[:notice] = 'Thanks for Your Message' format.html { redirect_to root_path } </pre> <p>Later in this lesson, we’ll further modify this code to actually send the messages as an email, in addition to saving it to the database.</p> <h3>Message validations</h3> <p>We want to be sure that the message looks valid before processing it, so we add the following validations to the message model (models/message.rb):</p> <pre> validates_presence_of :name, :subject, :body validates_format_of :email, :with =&gt; /^(\S+)@(\S+)\.(\S+)$/ </pre> <p>The second validation ensures that the email looks like a valid email address. This messy regular expression is one of the simpler ones of many that could be used.</p> <p>To apply some styling to the error messages that are displayed if a validation fails, modify the stylesheet line in application.html.erb:</p> <pre> &lt;%= stylesheet_link_tag 'learningrails', 'scaffold' %&gt; </pre> <p>This includes the standard Rails <code>scaffold.css</code> file, which the rails script created as part of the initial creation of the application.</p> <h3>Fetching the Page Object</h3> <p>To keep the Contact Us tab highlighted while the contact form is displayed, and to set the page title, we need to fetch the page object and set the pagetitle instance variable. We did this already for the links_controller/list action in the <a href="/learningrails/17">Lesson 17</a>; let’s extract that code, and put it in a method that we store in application.rb:</p> <pre> def get_page_metadata @page = Page.find_by_name(params[:name]) @pagetitle = @page.title end </pre> <p>Now we can use this same method in both links_controller/list and messages_controller/new. In the future, we’ll include this method in any action to which we’re redirecting via the <span class="caps">CMS</span>.</p> <h3>Setting Up the Messages Admin</h3> <p>For convenience, we’ll edit the admin page (using the <span class="caps">CMS</span>) to add a link to the message admin:</p> <pre> "Message Admin":/messages</pre> <p>We don’t want site visitors being able to view the list of contact messages, so we need to add authentication to the contacts controller. But we do need the “new” action in this controller to be accessible to visitors, as well as the “create” action that is invoked by the form when it is submitted. So add the following line to the start of controllers/messages_controller.rb:</p> <pre> before_filter :login_required, :except =&gt; [:new, :create] </pre> <h2>Preparing to Send the Message</h2> <h3>Setting Up the Mailer</h3> <p>Thanks to the scaffold generator and our <span class="caps">CMS</span>, we hardly had to write any code to create the contact form or the admin interface that allows us to read them. That was the easy part — now we want to generate an email to the web site manager with the contents of the message.</p> <h3>Creating the Mailer</h3> <p>The first step is to create a special Rails model called a mailer. As with other things in the Rails world, there’s a generator to make them. The command is:</p> <pre> script/generate mailer contact_mailer message </pre> <p>This creates a mailer model, called contact_mailer, and its associated view, which we’ve called message. You can enter multiple views here, for example if you had different kinds of contact forms and wanted to format each message differently.</p> <h3>Configuring How Mail is Sent</h3> <p>To actually send mail, we need to tell Rails how it supposed to access the mail system. You can configure it to use an <span class="caps">SMTP</span> server, or you can tell it to use sendmail, which is the more common approach if you’re on a Unix-type system; you’ll need a few details from the system administrator for the configuration for settings. In development mode, you’ll probably want to use an <span class="caps">SMTP</span> server to which you have access for testing. For example, here’s the configuration for sending email via <span class="caps">SMTP</span> over a Comcast connection:</p> <pre> ActionMailer::Base.delivery_method = :smtp ActionMailer::Base.smtp_settings = { :address =&gt; 'smtp.comcast.net', :domain =&gt; 'comcast.net' } </pre> <p>We’ll add this code to config/environments/development.rb. You’d need to add similar code to production.rb for whatever <span class="caps">SMTP</span> server you’re using in your production environment.</p> <p>You can use :test for the method instead of :smtp, in which case all the messages are stored in an array, instead of being sent out.</p> <p><span class="caps">SMTP</span> servers are picky about what mail they will accept, so if you’re not on a Comcast connection you’ll have to modify these settings. You can also specify additional parameters, including the port number, if it is not the default 25, as well as a user name and login if authentication is required.</p> <p>If you’re having trouble getting your mail to go out in development mode, you may want to change this line in your development environment:</p> <pre> config.action_mailer.raise_delivery_errors = false </pre> <p>And set it to <code>true</code> instead. If you don’t do that, delivery failures are silently ignored in development mode.</p> <h3>Setting Up the Mailer Model</h3> <p>Once you’ve run the generator, you’ll find a file contact_mailer.rb in your models folder. Open that file, and you’ll find one method, <code>message</code>. The method is in a class that inherits from ActionMailer::Base, rather than ActiveRecord::Base like all your other models, so it has a different set of characteristics and capabilities.</p> <p>We’re going to replace this method with the following:</p> <pre> def message(message) subject message.subject body :message =&gt; message recipients CONTACT_RECIPIENT from message.email sent_on Time.now end </pre> <p>Note that we’re not setting variables with names like subject; we’re invoking methods, which are available to all mailer methods, and passing parameters to those methods (earlier versions of rails used instance variables instead).</p> <p>The subject is simple enough; we’re just setting the parameter for the subject method to the subject attribute of the message, which we’ve passed into this method.</p> <p>For the body, we provide a hash that has the name of the instance variable we want to pass, and the value of that variable. We have only one variable here; you could have several. This passes the message object to an instance variable named @message that will be available in the view.</p> <p>Now we set the recipient to a constant, rather than a literal value, because we don’t want a specific email address, which may change in time, deep in our code. Add this line in your config/environments/development.rb file:</p> <pre> CONTACT_RECIPIENT = 'yourname@yourdomain.com' </pre> <p>Another benefit of this approach is that you can set a different address in your production.rb environment file. For example, in development, you’ll probably want contact messages to go to you, but in production, they typically go to an administrative or sales person.</p> <p>Finally, we set the “from” address and the time.</p> <p>That’s it for the mailer model. Now on to the view.</p> <h3>Creating the Mailer View</h3> <p>You’ll find an empty view in views/contact_mailer/message.erb. This is the view that will get invoked when we use the mailer model to request deliver of a message.</p> <p>The instance variables passed to this view are those we defined in the <code>@body</code> variable in the model. Now we use them just like in a regular view, but remember that we’re generating a plain-text email here, so there’s no <span class="caps">HTML</span> code required. Everything we need is in the @message variable, which holds the message object created from the form. Here’s a simple view:</p> <pre> Email from your web site From: &lt;%= @message.name %&gt; Company: &lt;%= @message.company %&gt; Phone: &lt;%= @message.phone %&gt; Message: &lt;%= @message.body %&gt; </pre> <h2>Delivering the Mail</h2> <p>With all this setup behind us, actually delivering the mail is easy. Open the file controllers/messages_controller.rb, and add this line immediately after “if @message.save” (we only want to deliver the mail if the save was successful, indicating that any validations passed):</p> <pre> ContactMailer.deliver_message(@message) </pre> <p>We’re invoking the message method in the ContactMailer class (defined in models/contact_mailer.rb). We pass to that method the message object from the form.</p> <p>There’s one strange thing here: the method we’re invoking is “deliver_message”, not “message”. Rails creates this method name for us, and we have to use it. Just one of those oddities to get used to. (This odd syntax exists to support another option: you can call the create_message method, which will produce the mail object, ready to be sent, but won’t actually send it.)</p> <p>You can now start the server and create a contact message, and it should be sent. Depending on what system you’re running on, and how your mail delivery is configured, it may not actually go out, but you can look at the Rails log (which should be in the console window in which you started the server), and it will show the email that was generated, even if it couldn’t contact a mail system to actually send it.</p> <h2>Sending <span class="caps">HTML</span> and Multipart email</h2> <p>You can easily send <span class="caps">HTML</span> or multipart email as well. If you simply name your view files appropriately, Rails will automatically produce multipart mail, so mail readers that accept <span class="caps">HTML</span> will get that, and others will get text.</p> <p>Rename message.erb to message.text.plain.erb, and make a new file in that same folder called message.text.html.erb. In that file, put an <span class="caps">HTML</span> version of the message, such as:</p> <pre> &lt;h1&gt;Email from your web site&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;From: &lt;%= @message.name %&gt;&lt;/li&gt; &lt;li&gt;Company: &lt;%= @message.company %&gt;&lt;/li&gt; &lt;li&gt;Phone: &lt;%= @message.phone %&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Message: &lt;%= @message.body %&gt;&lt;/p&gt; </pre> <p>Now when the message is sent, it will be sent as a multipart message.</p>