Add Contact Form to Hartl Tutorial
I am learning Rails by following the Hartl tutorial and making my own adjustments to it. Now I would like to expand on it and add a contact form that sends an email message. This is not included in the tutorial, but at the end of Chapter 10 we learned how to use the mailer method and we configured SendGrid on Heroku.
I already have a view set up on routes and I think it will require the following additional steps:
1) Terminal. rails creates a ContactForm mailbox
2) In app / mailers / contactform.rb:
def send_contactform_email(visitor)
@visitor = visitor
mail( :to => myemail@example.com, :from => visitor.email, :subject => 'Contact form' )
end
3) app / views / contactform_mailer / (mailing view), for example:
<h1>Website contact form</h1>
<p>On <$= ... %> <%= "#{@visitor.name} (#{@visitor.email} sent the following message:" %></p>
<p><%= @visitor.message %></p>
4) app \ controllers \ static_pages_controller (or other location?)
# Sends contact form email.
def send_contact_form_email
ContactFormMailer.send_contactform_email(visitor).deliver_now
redirect_to contact_path, notice: 'Message sent'
end
5) app \ views \ static_pages \ contact.html.erb (I'm not sure about the first line, should I also do something in route.rb? My guess is the first line, tell me to execute the method in step 4, which won't work as it does now.)
<%= form_for(:static_pages, url: contactform_path) do |f| %>
<i class="pt-row-icon glyphicon glyphicon-user"></i> <%= f.label :name %>
<%= f.text_field :name, placeholder: 'Name', class: 'form-control' %>
<i class="pt-row-icon glyphicon glyphicon-envelope"></i> <%= f.label :email %>
<%= f.email_field :email, placeholder: 'Email', class: 'form-control' %>
<i class="pt-row-icon glyphicon glyphicon-envelope"></i> <%= f.label :message %>
<%= f.text_area :message, placeholder: 'Your messageβ¦', class: 'form-control' %>
<%= f.submit "Send", class: "btn btn-primary" %>
<% end %>
I don't think this is 100% correct, especially the bold sections. What are your thoughts?
UPDATE VERSION 2: I tried to do updates as suggested by Ven and there is now the code below. The idea, as I understand it, is that
- the controller in
def contact
sets the @message variable. - form_for knows to fill this variable with [: message] parameters.
- the controller takes values ββfrom form_for and passes them to the mailer.
- the mailer uses the mailer view to compose the message to send.
- the sender sends it back to the controller, which sends the message.
1) Application / controllers / static_pages_controller.rb
class StaticPagesController < ApplicationController
before_action :valid_email?, only: [:send_message_email]
# Shows the contact form page
def contact
@message = message(message_params)
end
# Sends the message.
def send_message_email
@message = message(message_params)
if @message.valid?
MessageMailer.new_message(@message).deliver_now
redirect_to contact_path, notice: "Your messages has been sent."
else
flash[:alert] = "An error occurred while delivering this message."
render :new
end
end
private
def message_params
params.require(:message).require(:name, :email, :content)
end
def valid_email?(email)
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
email.present? && (email =~ VALID_EMAIL_REGEX)
end
end
2) Contact form in app \ views \ static_pages \ contact.html.erb:
<%= form_for(message: params[:message], url: contact_path) do |f| %>
<%= f.label :name %> <%= f.text_field :name, placeholder: 'Name', class: 'form-control' %>
<%= f.label :email %> <%= f.email_field :email, placeholder: 'Email', class: 'form-control' %>
<%= f.label :content %> <%= f.text_area :content, placeholder: 'Your messageβ¦', class: 'form-control' %>
<%= f.submit "Send", class: "btn btn-primary" %>
<% end %>
3) Routes.rb
get 'contact' => 'static_pages#contact', as: 'contact'
post 'contact' => 'static_pages#send_message_email'
4) App / views / message_mailer.text.erb (and html.erb)
<%= @message[:name] %> <%= @message[:email] %> wrote:
<%= @message[:content] %>
5) App / mailers / message_mailer.rb
class MessageMailer < ApplicationMailer
default to: "myemail@example.com>"
def new_message(message)
@message = message
mail to: "myemail@example.com"
mail from: @message[:email]
mail subject: "Message from #{message[:name]}"
end
end
Now, when I try to visit the contact form on the server, I get an error message: param is missing or the value is empty: message
. It refers to a string params.require(:message).require(:name, :email, :content)
. Not sure what I am doing wrong. Changing it to params.require(:message).permit(:name, :email, :content)
doesn't matter.
source to share
4) app \ controllers \ static_pages_controller (or some other place?)
This seems to be correct if it is a github replica for the said application.
def send_contact_form_email
The controller has a problem: This action will try to send an email, it doesn't matter if it is used in POST or GET. You have to use two different actions, one to display the view (using GET) and one to send email (using the mailer class you created). (you may need to create another controller for now)
ContactFormMailer.send_contactform_email(visitor).deliver_now
Then moving on: what you pass to your email program is a "visitor". There is no such variable. You probably want to access something from the hash params
(which contains parameters for GET and POST requests) and use the same key as your form ( form_for(:visitor ...
=> params[:visitor]
(so you want to change that :static_pages
)).
<p>On <$= ... %> <%= "#{@visitor.name} (#{@visitor.email} sent the following message:" %></p>
Since this returns an object and not a hash, @visitor.email
must be @visitor[:email]
inside the mailer.
One last thing: just use params[:visitor]
would mean people can leave field gaps. You might want to look into the powerful options that were added in Rails 4 (does the book seem a little outdated?).
And finally, you need to add routes to be able to achieve these actions (one for the GET request - display the view - and one for the POST request - submit the form).
PS:
mail( :to => myemail@example.com, :from => visitor.email, :subject => 'Contact form' )
Warning: you forgot to enter your email address here. Also, you've changed the to
/ from
. You want to send TO your client's email address, not from him.
EDIT
params.require(:message).require(:name, :email, :content)
This will require the specified keys, but AFAIK at the same level as :message
- top. You want to use permit
:
params.require(:message) # require "namespace"
.permit(:name, :email, :content) # permit keys
@message = message(message_params)
Where is the function defined message
?
mail to: "myemail@example.com"
mail from: @message[:email]
mail subject: "Message from #{message[:name]}"
This sends 3 different emails since you called the function mail
3 times.
source to share