Adding a Contact Form and Mailer
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:
- Learning Rails example app code as of the start of this lesson
- Learning Rails example app code as of the end of this lesson
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.
Comments on This Lesson
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 => 'gmail.com',
:user_name => 'yourname@gmail.com',
:password => 'yourpasswordhere',
:port => '465',
:address => 'smtp.gmail.com',
:authentication => :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




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.