-
Notifications
You must be signed in to change notification settings - Fork 0
Backend Training: Form Helper
Next, from the mock up we need a contact page with a form for the visitor to submit inquiries to us.
We already have a contact model from previous section so let's add a controller for contact.
rails g controller contacts
Go to app/controllers/contacts_controller.rb
and add..
# app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def new; end
def create; end
end
And let's add routes for contacts
# config/routes.rb
Rails.application.routes.draw do
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
%w[404 401 500 503].each do |code|
get code, to: 'errors#show', code: code
end
resources :faqs, only: %i[index]
resources :posts, only: %i[index show]
resources :contacts, only: %i[new create]
get '/pricing' => 'home#pricing', as: :pricing
root 'home#index'
end
p.s. here we are using the new and create actions because new is initializing a new object and create is creating an object in standard CRUD actions
Let's verify that the routes have been added properly
$ rails routes | grep contacts
contacts POST /contacts(.:format) contacts#create
new_contact GET /contacts/new(.:format) contacts#new
Great we have the contact routes.
Next, lets add a new template for the new action
# app/views/contacts/new.slim
section.jumbotron[id='hero']
.container
.row.text-center
.col-md-12
h1= t('.hero.title')
section[id='contact-info']
.container
.row
.col-md-12
h2= t('.contact_info.title')
.row
.col-md-5
p= t('.form.description')
.col-md-7
strong= t('.info.contact.title')
ul
li
= fa_icon('phone')
|
= t('.info.contact.phone')
li
= fa_icon('envelope')
|
= t('.info.contact.email')
li
= fa_icon('map-marker')
|
= t('.info.contact.address')
br/
strong= t('.info.hours.title')
p== t('.info.hours.body')
Add translations
# config/locales/en.yml
en:
......
contacts:
new:
hero:
title: Contact Us
contact_info:
title: Let’s Get Started!
form:
description: Ready to talk about your project? Need a quote or just want to bounce ideas off us? We’d love to help. Bring us your ideas, lists or drawings - we’ll put the kettle on.
info:
contact:
title: Contact Information
phone: (778) 806-1108
email: Email Us
address: 5050 Kingsway, Unit 412, Burnaby, BC, V5H4H2
hours:
title: Office Hours
body: Monday-Friday, 9:30AM - 5:30PM </br> We are closed on Canadian Holidays
Let's go to /contacts/new
and you should get something like...
Great! Let's add a link to this page on the navbar.
# app/views/components/_navbar.slim
nav.navbar.navbar-default
.container
.navbar-header
......
.collapse.navbar-collapse[id='main-nav']
ul.nav.navbar-nav.navbar-right
......
li= link_to t('.contact'), new_contact_path
And translation for it
# config/locales/en.yml
en:
......
components:
navbar:
home: HOME
about: ABOUT
pricing: PRICING
pricing/basic: Basic Package
pricing/advance: Advance Package
faqs: FAQS
posts: NEWS
contact: CONTACT
......
Refresh the browser Command ⌘+r, and the link should show up.
🔖 : Form Helper
, simple_form
, rails-i18n
Form helper works well with ActiveRecord Model objects so let's add a new record to the controller.
# app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def new
@contact = Contact.new
end
def create; end
end
Now, we have the object so lets go ahead and add the form.
# app/views/contacts/new.slim
section.jumbotron[id='hero']
.container
.row.text-center
.col-md-12
h1= t('.hero.title')
section[id='contact-info']
.container
.row
......
.row
.col-md-5
p= t('.form.description')
= simple_form_for @contact do |f|
= f.input :name
= f.input :email
= f.input :message
= f.button :submit
.col-md-7
......
Refresh the browser Command ⌘+r, and you should see something like...
Great! We have a form but our form doesn't exactly look like the one on the mockup. Let's change it to reflect the mockup.
# app/views/contacts/new.slim
section.jumbotron[id='hero']
.container
.row.text-center
.col-md-12
h1= t('.hero.title')
section[id='contact-info']
.container
.row
......
.row
.col-md-5
p= t('.form.description')
= simple_form_for @contact do |f|
= f.input :name,
label: false
= f.input :email,
label: false
= f.input :message,
label: false,
input_html: { rows: 5 }
= f.button :submit,
class: 'btn-primary'
.col-md-7
......
Let's add translations for the forms.
The submit button translation is using rails default form helper translations so we need to change it by overriding it under helpers. For complete list of rails default translations please see doc
# config/locales/en.yml
en:
helpers:
submit:
contact:
create: Submit
.......
The placeholders for the fields are using simple_form translations so let's add them inside simple_form.en.yml
# config/locales/simple_form.en.yml
en:
simple_form:
"yes": 'Yes'
"no": 'No'
required:
......
error_notification:
......
placeholders:
contact:
name: Full Name*
email: Email Address*
message: Message
# Examples
.......
Refresh the browser Command ⌘+r, and you should see something like...
Great! Our form now looks a lot like the mockup.
🔖 'Strong Params' Next let's hit the create button and see what happens. From your server logs you should see...
Started POST "/contacts" for 127.0.0.1 at 2017-11-30 12:41:45 -0800
Processing by ContactsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"chmrIP70VaNwnTrwH0yw+AnpDOqnkz6YD6TEsPZD2wDTEqWIQ7URj3Rx57D1mj5oQpis3pA4/+WBLtdCFhJ8vw==", "contact"=>{"name"=>"", "email"=>"", "message"=>""}, "commit"=>"Submit"}
No template found for ContactsController#create, rendering head :no_content
Completed 204 No Content in 383ms (ActiveRecord: 0.0ms)
Looks like it is going to the create action inside contacts_controller. Let's modify our create controller so we can create an object. Here we need use of Rails' Strong Params as part of the security measure to allow only the params we whitelisted. Let's also add a debugger to fully understand the logic behind this.
# app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def new
@contact = Contact.new
end
def create
debugger
@contact = Contact.new contact_params
end
private
def contact_params
params.fetch(:contact, {}).permit(
:name, :email, :message
)
end
end
Ok, let's clear out the logs by pressing Command+k in your server terminal, and click on the submit button again.
You should see something like inside your server terminal.
[4, 13] in /Users/ilunglee/Work/rails/mock_project/app/controllers/contacts_controller.rb
4: @contact = Contact.new
5: end
6:
7: def create
8: debugger
=> 9: @contact = Contact.new contact_params
10: end
11:
12: private
13:
(byebug)
Great! Let's try debugging this part of the code.
(byebug) params
<ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"B3HtQGAPohBb0cVmXpAOG2E/CSSX7NSEARXcyWu/OLemeuPo3U7mPF89GCa0RoCLKk6pEKBHFfmPn887i+6fCA==", "contact"=>{"name"=>"", "email"=>"", "message"=>""}, "commit"=>"Submit", "controller"=>"contacts", "action"=>"create"} permitted: false>
params
gives us all the parameters send from the browser to the server, and we see that all the contact form parameters are wrap inside the contact hash "contact"=>{"name"=>"", "email"=>"", "message"=>""}
.
(byebug) params.fetch(:contact)
<ActionController::Parameters {"name"=>"", "email"=>"", "message"=>""} permitted: false>
params.fetch(:contact)
gives us all the parameters inside contact hash.
(byebug) params.fetch(:contact, {})
<ActionController::Parameters {"name"=>"", "email"=>"", "message"=>""} permitted: false>
params.fetch(:contact, {})
gives us all the parameters inside contact hash as well but will return empty hash {}
if contact is not found instead of returning errors.
Let's try this by fetching a non-existing parameter
(byebug) params.fetch(:post)
*** ActionController::ParameterMissing Exception: param is missing or the value is empty: post
nil
params.fetch(:post)
throws an error because post does not exist inside the parameters.
(byebug) params.fetch(:post, {})
<ActionController::Parameters {} permitted: false>
params.fetch(:post, {})
returns an empty hash instead of error.
Great now we understand this part, next, we should investigate on the permitted attribute. From the above examples you should see that in every params log it all shows permitted: false
. This is cause by Rails strong parameters to discard any parameters that are not explicitly allowed by the server.
To fix this we will add our parameters to the whitelist by...
(byebug) params.fetch(:contact, {}).permit(:email)
Unpermitted parameters: name, message
<ActionController::Parameters {"email"=>""} permitted: true>
Now, we see that email is permitted but it also shows Unpermitted parameters: name, message
. Let's fix this by add all of them to permit method.
(byebug) params.fetch(:contact, {}).permit(:name, :email, :message)
<ActionController::Parameters {"name"=>"", "email"=>"", "message"=>""} permitted: true>
Great! All our parameters are permitted by the controller. Type c
to continue.
(byebug) c
No template found for ContactsController#create, rendering head :no_content
Completed 204 No Content in 783198ms (ActiveRecord: 0.0ms)
Let's move on to add the functionality to save contact inside the create action.
# app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def new
@contact = Contact.new
end
def create
@contact = Contact.new contact_params
@contact.save
render 'new'
end
......
end
Refresh the browser Command ⌘+r, and click on submit again...
Great! Our form is working. Let's commit and move on to the next section. p.s. Fix all the overcommit errors and commit
$ git add -A
$ git commit -m 'Contact Form Setup'