By using my site you consent to my use of cookies. Learn More.

15 November, 2016

Build a Contact Form with Ruby on Rails (part 3)

Build a Contact Form with Ruby on Rails (part 3)

This is part 3 of my series on building a contact form with Ruby on Rails 5. This part will focus on forwarding received messages to my private email address.

The other parts of this series can be found at the following links:

There is a Github repo for this tutorial, and you should probably read part 1 and part 2 before attempting to follow any further.

Quick Recap

In part 2 we built the controller and view for our contact form, so visitors can send messages to our website. Now we are going to learn how to forward those messages on to a private email address.

Are you ready?

Sending Emails With ActionMailer

Now that we have a working model, view and controller, we need to configure this app to send emails.

Rails provides a generator that will help us get started here:

bin/rails generate mailer Message contact_me

This creates a class named MessageMailer, found at app/mailers/message_mailer.rb, containing an action named contact_me, and a few other useful files.

Open the newly created test file found at test/mailers/message_mailer_test.rb and make it look like this:

require 'test_helper'

class MessageMailerTest < ActionMailer::TestCase

  test "contact_me" do
    message = Message.new name: 'anna',
                          email: [email protected]',
                          body: 'hello, how are you doing?'

    email = MessageMailer.contact_me(message)

    assert_emails 1 do
      email.deliver_now
    end

    assert_equal 'Message from www.murdo.ch', email.subject
    assert_equal [[email protected]'], email.to
    assert_equal [[email protected]'], email.from
    assert_match /hello, how are you doing?/, email.body.encoded
  end
end

Pfft, it looks like there's a lot going on there, but it's actually quite simple, so allow me to explain.

We begin by creating a Message from my friend Anna, whose email is [email protected], which we pass, as an argument, to the contact_me method of our brand new MessageMailer class. The result is stored as a local variable named email.

Then, from within an assert_emails block, we call deliver_now on our email, and make sure that exactly 1 email was queued. Finally we make some assertions about the email.

The test will fail when we run it, with bin/rails test:mailers, but you knew that already:

bin/rails test:mailers

MessageMailerTest#test_contact_me:
ArgumentError: wrong number of arguments (given 1, expected 0)
    app/mailers/message_mailer.rb:3:in `contact_me'

// omitted

Ahh, the MessageMailer#contact_me method, which was generated by Rails, does not take any arguments yet, and we are trying to pass a Message object in to it.

Open app/mailers/message_mailer.rb and modify the contact_me method to take an argument like so:

class MessageMailer < ApplicationMailer

  def contact_me(message)
    @greeting = "Hi"

    mail to: "[email protected]"
  end

end

That's better, our method now takes 1 argument called message. Incidentally, the code inside the contact_me method was added by Rails, leave it there for now, we'll fix it up in a moment.

Setting the Subject

Run the mailer test again, and this time you should see the following failure message:

Failure:
MessageMailerTest#test_contact_me:
--- expected
+++ actual
@@ -1 +1,2 @@
-"Message from www.murdo.ch"
+# encoding: US-ASCII
+"Contact me"

This failure message is happening because one of our assertions specifies that the subject of the email should be "Message from www.murdo.ch", but Rails has set it to "Contact me".

So we need to set the subject explicitly, and the best place to do that is in config/locales/en.yml:

en:
  hello: "Hello world"

  message_mailer:
    contact_me:
      subject: "Message from www.murdo.ch"

There are other ways to set the subject of an email with Rails, but I prefer this approach as it keeps my mailer files cleaner and easier to look at.

Sending Emails To My Private Address

Run the mailer test again with bin/rails test:mailers and you should see the following error message:

MessageMailerTest#test_contact_me:
Expected: ["[email protected]"]
  Actual: ["[email protected]"]

Rails is complaining because I have asserted that the email should be sent to [email protected], my private address, but it is going to [email protected].

Change the contact_me method to mail [email protected], or whatever address you want your messages to be forwarded to. Hint: you probably want to use your own personal email address here.

class MessageMailer < ApplicationMailer

  def contact_me(message)
    @greeting = "Hi"

    mail to: "[email protected]"
  end

end

Now run the test again and it will fail with a different message:

Failure:
MessageMailerTest#test_contact_me:
Expected: ["[email protected]"]
  Actual: ["[email protected]"]

OK, Rails is complaining because I have specified that the email should be from [email protected], but it's from [email protected].

We can fix this by changing the contact_me method to this:

  def contact_me(message)
    @greeting = "Hi"

    mail to: "[email protected]", from: message.email
  end

Do you understand what we did there? We're telling Rails to make sure the email it sends to [email protected] is from the email address used to create the Message, in this case [email protected].

Adding The Body

If you run the test again, it will fail with the following error message:

Failure:
MessageMailerTest#test_contact_me:
Expected /hello, how are you doing?/ to match # encoding: US-ASCII

// lots of output omitted

This time Rails is complaining because the body of our email is not the string we expected it to be. e

We need to do three things to fix this. First, we need to make the following change to the contact_me method from our MessageMailer class:

  def contact_me(message)
    @greeting = 'Hi'
    @body = message.body

    mail to: "[email protected]", from: message.email
  end

We've create a new instance variable, called @body, to store the body of the message that Anna sent us.

Next, we need to open up the file found at app/views/message_mailer/contact_me.html.erb and replace everything inside it with the following single line of code:

<%= @body %>

And finally we need to make the exact same change to the file found at app/views/message_mailer/contact_me.text.erb, the plain text version of the email. So open the file, delete everything in it, and add the following so that it looks exactly the same as it's html counterpart:

<%= @body %>

Now our test will pass!

You can also go ahead and remove the @greeting variable from the contact_me method if you like as we are no longer using it in our views.

A Quick Clarification

For the sake of clarity, your MessageMailer class should look like this:

class MessageMailer < ApplicationMailer

  def contact_me(message)
    @body = message.body

    mail to: "[email protected]", from: message.email
  end
end

And your view files should simply reference the @body instance variable from the contact_me method.

Hooking ActionMailer Into Our Messages Controller

So we've got a working Mailer class that sends emails, and now all we need to do is configure Rails to call it whenever the user submits a valid message.

Let's start off with a test.

Open test/controllers/messages_controller_test.rb, find the POST create test that we wrote in part 2 of this tutorial series, and change it to look like this:

require 'test_helper'

class MessagesControllerTest < ActionDispatch::IntegrationTest

  # previous test omitted

  test "POST create" do
    assert_difference 'ActionMailer::Base.deliveries.size', 1 do
      post create_message_url, params: {
        message: {
          name: 'cornholio',
          email: [email protected]',
          body: 'hai'
        }
      }
    end

    assert_redirected_to new_message_url

    follow_redirect!

    assert_match /Message received, thanks!/, response.body
  end

  # other test omitted

end

Do you get what we're doing here? We are asserting that a successful post to the create_messages_url route should increase the size of the ActionMailer::Base.deliveries array by 1, indicating that an email was sent as a result of the action performed.

This test fails when we run bin/rails test:controllers because our messages controller has not yet been instructed to invoke the Mailer, therefore the ActionMailer::Base.deliveries array will be empty.

You should see the following error message when you run the test:

Failure:
MessagesControllerTest#test_successful_post:
"ActionMailer::Base.deliveries.size" didn't change by 1.
Expected: 1
  Actual: 0

We can fix this by opening app/controllers/messages_controller.rb and calling MessageMailer.contact_me from it like so:

class MessagesController < ApplicationController
  # previous method omitted

  def create
    @message = Message.new(message_params)

    if @message.valid?
      MessageMailer.contact_me(@message).deliver_now
      redirect_to messages_new_url, notice: "Message received"
    else
      render :new
    end
  end

  # private method omitted
end

In case it's not clear, we have instructed Rails to call the Mailer if the message received from the user is valid.

With this in place our controller test will pass when we run it.

Email Previews

So our app sends emails now, but what do they look like? Well Rails has a handy solution that lets us preview emails in development mode. Open the file found at test/mailers/previews/message_mailer_preview.rb. It should look like this:

# Preview all emails at http://localhost:3000/rails/mailers/message_mailer
class MessageMailerPreview < ActionMailer::Preview

  # Preview this email at http://localhost:3000/rails/mailers/message_mailer/contact_me
  def contact_me
    MessageMailer.contact_me
  end

end

Change it to this:

# Preview all emails at http://localhost:3000/rails/mailers/message_mailer
class MessageMailerPreview < ActionMailer::Preview

  # Preview this email at http://localhost:3000/rails/mailers/message_mailer/contact_me
  def contact_me 
    message = Message.new name: 'marflar', 
                          email: [email protected]',
                          body: 'This is the body of the email'

    MessageMailer.contact_me message
  end

end

Now start the server with bin/rails server and visit http://localhost:3000/rails/mailers/message_mailer/contact_me to see what your email will look like.

What Now?

Well we have an awesome contact form, that works in development and test environments. Getting it into production is another matter, and one that depends entirely on your hosting setup. I may write about that later on.

There's a lot you could do from here, like adding Ajax and client side validations using something like Zurb Foundation's Abide plugin, and maybe I'll write about those things later on too.

I hope you found this tutorial useful, please let me know if you encountered and bugs or typos as I've likely made a few. You can get me on [email protected] or through my own contact form.

Don't forget that there is a Github repo for this tutorial, and the other parts of the series can be found at the following links:

Amen.