Table of Contents

View Screencast (Quicktime)
Right-click and choose Save As to save file to your computer


Sign Up Now!

If you aren’t already receiving our course lessons via email, sign up now to be sure you don’t miss anything.

Every few days, we’ll send you an email with a link to the next episode, plus a list of additional resources for advancing your knowledge.

There’s no cost and no obligation. And we’ll never share your email address with any third party.


We’ll send you the first lesson right away.


Want to help spread the word? We’d be grateful if you would include a link to the course in your blog, web site, or emails.

Adding a Contact Form and Mailer

9 comments

Goals

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.

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.

Setup

We begin with the code with which we ended Lesson 17. These zip files contain the beginning and ending states of the code:

Creating the contact form

Scaffolding the Message Model

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.

We start by creating a scaffold for contacts:

script/generate scaffold message name:string company:string phone:string email:string subject:string body:text

This generates the model, controller, views, and test files.

Now run the migration:

rake db:migrate

And start the server:

script/server

Hooking up the Contact Form

First delete the layout file the scaffold generates (views/layouts/messages.html.erb), so the scaffolded views will use our standard layout.

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 Lesson 17) to the new action in the messages controller.

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:

<%= link_to 'Back', messages_path %>

Redirecting to Home after Submission

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.

In the message controller’s show action:

if @message.save
  flash[:notice] = 'Thanks for Your Message'
  format.html { redirect_to root_path }

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.

Message validations

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):

validates_presence_of :name, :subject, :body
validates_format_of :email, :with => /^(\S+)@(\S+)\.(\S+)$/

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.

To apply some styling to the error messages that are displayed if a validation fails, modify the stylesheet line in application.html.erb:

<%= stylesheet_link_tag 'learningrails', 'scaffold' %>

This includes the standard Rails scaffold.css file, which the rails script created as part of the initial creation of the application.

Fetching the Page Object

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 Lesson 17 let’s extract that code, and put it in a method that we store in application.rb:

def get_page_metadata
  @page = Page.find_by_name(params[:name])
  @pagetitle = @page.title
end

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

Setting Up the Messages Admin

For convenience, we’ll edit the admin page (using the CMS) to add a link to the message admin:

"Message Admin":/messages

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:

before_filter :login_required, :except => [:new, :create]

Preparing to Send the Message

Setting Up the Mailer

Thanks to the scaffold generator and our CMS, 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.

Creating the Mailer

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:

script/generate mailer contact_mailer message

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.

Configuring How Mail is Sent

To actually send mail, we need to tell Rails how it supposed to access the mail system. You can configure it to use an SMTP 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 SMTP server to which you have access for testing. For example, here’s the configuration for sending email via SMTP over a Comcast connection:

ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
  :address => 'smtp.comcast.net',
  :domain => 'comcast.net'
}

We’ll add this code to config/environments/development.rb. You’d need to add similar code to production.rb for whatever SMTP server you’re using in your production environment.

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.

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

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:

config.action_mailer.raise_delivery_errors = false

And set it to true instead. If you don’t do that, delivery failures are silently ignored in development mode.

Setting Up the Mailer Model

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

We’re going to replace this method with the following:

def message(message)
  subject    message.subject
  body       :message => message
  recipients CONTACT_RECIPIENT
  from       message.email
  sent_on    Time.now
end

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

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.

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.

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:

CONTACT_RECIPIENT = 'yourname@yourdomain.com'

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.

Finally, we set the “from” address and the time.

That’s it for the mailer model. Now on to the view.

Creating the Mailer View

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.

The instance variables passed to this view are those we defined in the @body 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 HTML code required. Everything we need is in the @message variable, which holds the message object created from the form. Here’s a simple view:

Email from your web site

From: <%= @message.name %>

Company: <%= @message.company %>

Phone: <%= @message.phone %>

Message: <%= @message.body %>

Delivering the Mail

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):

ContactMailer.deliver_message(@message)

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.

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

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.

Sending HTML and Multipart email

You can easily send HTML or multipart email as well. If you simply name your view files appropriately, Rails will automatically produce multipart mail, so mail readers that accept HTML will get that, and others will get text.

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 HTML version of the message, such as:

<h1>Email from your web site</h1>
<ul>
    <li>From: <%= @message.name %></li>
    <li>Company: <%= @message.company %></li>
    <li>Phone: <%= @message.phone %></li>
</ul>
<p>Message: <%= @message.body %></p>

Now when the message is sent, it will be sent as a multipart message.


Add a Comment

Have a comment or question about this lesson? Add it here.






Comments on This Lesson

From: Michael Slater       Date: 08/18/08 01:01 AM

Subject: Gmail

Did you put in a valid gmail user name and password? You need a valid gmail account to send mail through gmail. If errors are occurring, you should be able to see them in development.log.

From: Juarez P. A. Filho       Date: 08/18/08 12:00 AM

Subject: Another great lesson... But I want send an email

I’m really loving this course, a lot of things I have been learning. Michael I saw your message about use gmail as smtp server, so I put the code onto my development.rb and the e-mail wasn’t send yet. Could you help me please? My file is: http://pastie.org/private/bp5sxzu1×4pbsvp2bazw

Thanks a lot. juarezpaf.com

From: Jason       Date: 06/30/08 06:18 PM

Subject: a bit of help

Christopher, thank you for taking the time to write me back. It seems like the database was the issue. I was using my own database (MySql), and it was missing all of the information that was entered during the lesson. I’m going to use the database included with the lesson, and see where that takes me.

thank you, Jason

From: Christopher Haupt       Date: 06/30/08 04:16 PM

Subject: RE: need a bit of help

Hi Jason,

Are you using the code and database we provide in the archive or are you using your own? Check your database to make sure the “home” page is defined in the app (look via the admin interface of the CMS). It looks from your trace like it is missing.

-Christopher

From: Jason       Date: 06/29/08 08:20 PM

Subject: Excellent Training, but need a bit of help

The training has been a huge help, and thank you for all of your time and efforts. I’m a bit stuck when trying to run the sample code. Any guidance would be greatly appreciated.

Jason

NoMethodError in ViewerController#show

You have a nil object when you didn’t expect it! The error occurred while evaluating nil.subpages

RAILS_ROOT: C:/ruby/Development/learningrails_18 Application Trace | Framework Trace | Full Trace

app/controllers/viewer_controller.rb:5:in `show’

c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:1158:in `send’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:1158:in `perform_action_without_filters’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:697:in `call_filters’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue’ c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/rescue.rb:199:in `perform_action_without_caching’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:678:in `perform_action’ c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache’ c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/query_cache.rb:8:in `cache’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:677:in `perform_action’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:524:in `send’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:524:in `process_without_filters’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:685:in `process_without_session_management_support’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/session_management.rb:123:in `process’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:388:in `process’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb:171:in `handle_request’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb:115:in `dispatch’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb:9:in `dispatch’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/webrick_server.rb:112:in `handle_dispatch’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/webrick_server.rb:78:in `service’ c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service’ c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run’ c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread’ c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start’ c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread’ c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start’ c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `each’ c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start’ c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start’ c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/webrick_server.rb:62:in `dispatch’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/commands/servers/webrick.rb:66 c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’ c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’ c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:496:in `require’ c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:342:in `new_constants_in’ c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:496:in `require’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/commands/server.rb:39 c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’ c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’ script/server:3

app/controllers/viewer_controller.rb:5:in `show’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:1158:in `send’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:1158:in `perform_action_without_filters’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:697:in `call_filters’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue’ c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/rescue.rb:199:in `perform_action_without_caching’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:678:in `perform_action’ c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache’ c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/query_cache.rb:8:in `cache’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:677:in `perform_action’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:524:in `send’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:524:in `process_without_filters’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:685:in `process_without_session_management_support’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/session_management.rb:123:in `process’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:388:in `process’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb:171:in `handle_request’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb:115:in `dispatch’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi’ c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb:9:in `dispatch’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/webrick_server.rb:112:in `handle_dispatch’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/webrick_server.rb:78:in `service’ c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service’ c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run’ c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread’ c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start’ c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread’ c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start’ c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `each’ c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start’ c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start’ c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/webrick_server.rb:62:in `dispatch’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/commands/servers/webrick.rb:66 c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’ c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’ c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:496:in `require’ c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:342:in `new_constants_in’ c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:496:in `require’ c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.2/lib/commands/server.rb:39 c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’ c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’ script/server:3

Request

Parameters:

{“name”=>”home”}

From: Michael Slater       Date: 06/26/08 04:16 PM

Subject: gmail as smtp server

You can use gmail using a configuration like this:

config.action_mailer.smtp_settings = {
  :domain =&gt; 'gmail.com',
  :user_name =&gt; 'yourname@gmail.com',
  :password =&gt; 'yourpasswordhere',
  :port =&gt; '465',
  :address =&gt; 'smtp.gmail.com',
  :authentication =&gt; :plain
}

From: paul       Date: 06/26/08 02:14 PM

Subject: gmail ?

how do I use Gmail as smtp server ?

From: Algis       Date: 06/23/08 04:04 AM

Subject: wait for more...

Thank You, good screencast!

From: Ray Rogers       Date: 06/11/08 02:14 PM

Subject: Please keep up the great work

Another good screencast. Thank you

 

Hosting Provided By

EngineYard.com fully managed Rails hosting

Sponsored By

New Relic Rails Performance Monitoring

FiveRuns Tuneup

Peepcode Screencasts