Adding Contact Forms in Rails 7 with Mail_Form Gem
This article will walk through creating a simple contact form in your Rails 7 app such as on the Railscoder site. We'll be using the Mail-Form Gem to make this easy.
First. head over to Ruby Gems and get the gemfile for Mail_Form. Then add it to your Gemfile declararion and run bundle.
# Gemfile
gem 'mail_form', '~> 1.9'
# shell
$ bundle install
The next thing you will want to do is generate the contact form using the builder in Mail_Form.
I am naming my form "Contact" as you will see in this example below.
# shell
$ rails generate mail_form Contact name:string email: string message:text
You will see it generate a contact.rb file in your models.
Open that file to add validations and change the headers.
When you first open the file, it looks like this:
# app/models/contact.rb
class Contact < MailForm::Base
attribute :name
attribute :email
attribute :message
def headers
{ to: "[email protected]" }
end
end
We need to make some changes to get the validations in there as well as send the email to the correct place. In the case below, I am validating that the form has a name, a valid email from a regex, and a message. I am also using the captcha validation from the gem called "nickname" that will ensure against bots filling out the form and spamming me. Just remember that when we create our views - we will need a hidden field on our form that will trick bots but real humans will never see it.
# app/models/contact.rb
class Contact < MailForm::Base
attribute :name, validate: true
attribute :email, validate: /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i
attribute :message, validate:true
attribute :nickname, captcha: true
def headers
{
to: "[email protected]", # change this to be the email you want sent to
subject: "Railscoder Contact Form",
from: "[email protected]", # change this to be the email it is coming from
reply_to: %("#{name}" <#{email}>)
}
end
end
Now, we need to generate the controller since that is not handled with the generator from the gem.
# shell
$ rails generate controller contacts
Now, let's define the actions for that controller.
# app/controllers/contact_controller.rb
class ContactsController < ApplicationController
def new
@contact = Contact.new
end
def create
@contact = Contact.new(params[:contact])
@contact.request = request
if @contact.deliver
redirect_to action: :sent
else
flash.now[:error] = 'Could not send message'
render :new, status: :unprocessable_entity
end
end
def sent
end
end
Now we create the views. In the app/views/contacts folder, we will need views for the corresponding actions we created in the Contacts Controller. So create the files "new.html.erb" and "sent.html.erb".
I will first create the new.html.erb file and add the code and html with the form. And remember, we need that hidden field for "nickname" to trick bots.
# app/views/contacts/new.html.erb
<div class="px-8 py-6 rounded-md shadow bg-white">
<h1 class="text-4xl text-center">Contact Railscoder</h1>
<hr>
<p>I'd love to hear from you. Send me a message below.</p>
<%= form_for @contact do |form| %>
<% if @contact.errors.any? %>
<div id="error_explanation" class="bg-rose-200 pt-3 pb-1 mb-4 px-4 rounded-md">
<div class="font-bold text-rose-700 text-sm pb-4"><%= pluralize(@contact.errors.count, "error") %> prohibited this page from being saved:</div>
<ul class="pb-0 mb-0">
<% @contact.errors.each do |error| %>
<li class="text-rose-600 pl-4 text-sm"><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field mb-6">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field mb-6">
<%= form.label :email %>
<%= form.email_field :email %>
</div>
<div class="field">
<%= form.label :message %>
<%= form.text_area :message %>
</div>
<div class="hidden">
<%= form.label :nickname %>
<%= form.text_field :nickname %>
</div>
<div class="actions flex justify-end">
<%= form.submit "Send Message" %>
</div>
<% end %>
</div>
And then we need the sent view file. This is the view that gets delivered back to the user once the fill out and submit the form. You can put anything you like here but I kept it simple with a confirmation that the user's message was sent.
# app/views/contacts/sent.html.erb
<div class="rounded bg-white shadow p-6">
<h1>Message Sent!</h1>
<p>Nice job! You successfully sent me a message. If needed, I will get back to you as soon as possible. In the meantime, check out all my latest articles above.</p>
</div>
Style all the views how you see fit. I am using Tailwind and some really basic styling for this example.
Now we need to add our routes or none of this will work. I added an aliased route to simplify the view name in the browser title bar.
# config/routes.rb
resources :contacts, only: [:new, :create ]
get '/contacts', to: 'contacts#new', as: 'contact'
get 'contacts/sent'
It didn't work out. I have a "contact" page in the navbar where should appear the contact form where I should set the route then and the view?