The goal of this chapter is to teach you how to send data to the server and how to turn that data into something useful.
In the last chapter we built a web page with authors, posts and comments. In this chapter we will focus on creating posts and comments.
Let's get started! Start your Rails application from last time with rails s
,
in the navigation, click on posts. You should see a screen like the following.
At the bottom, you should see a button named "New Post", click on it and you should be greeted with the following.
If you now fill out that form and press "Create Post", the screen will flash and you will be presented with the information you just entered on another screen, on another URL.
How did we get that data from our browser to the server? This is the focus of this chapter. Let's take a look at what our browser is doing. Go back to the posts screen, and click on "New Post" again. Now fill out the form with different data, right-click on any field and select "Inspect" or "Inspect Element" from the drop down menu. You screen should split in half either horizontally or vertically. This window is called the inspector.
You should see a couple of tabs at the top of the new window. One should be "Network", select it. There should be a "Persist Logs" of "Preserve Logs" check box, if it's unchecked, check it.
Now press "Create Post"!
The following should appear.
There should be a lot of entries in the network tab now. Scroll up until you
find one that begins with POST
, click on it and your inspector should split
in half. In the new panel you should see data related to that particular
request. Here we can see exactly what our browser sent to our server when we
clicked "Create Post".
If we now click on the "Params" tab. We can see which data was sent!
Here we can see all the data associated with our post. That data is prefixed
with post
and the attributes name is within square brackets []
. There are
three additional fields - authenticity_token
, commit
and utf8
. Of those
three commit
is something the browser added by its self, while Rails added
authenticity_token
and utf8
. Those two fields are used to enhance the
security of our application - they protect us and our users from
malicious actors that try to steal or manipulate data. Let's ignore those
fields for now and focus on the post[]
fields. This encoding is done by
the form it self.
Forms are used to gather a user's input, like text, numbers or files, and send them to the server to be stored or processed. They are the most basic way to talk to servers. In a later chapter we will learn about more ways in which we can communicate with our server.
Let's try to create our own form! We will make a form that solves second degree polynomials.
Before we begin we need to know how to solve those polynomials! All second
degree polynomials follow this form a * x^2 + b * x + c = d
. You may notice
that we can eliminate the d
variable by moving it to the other side so that
we get c - d
- which we can simply call c
as it's just another variable.
The general solution to those kinds of problems is
x = (-1 * b +- sqrt(b ^ 2 - 4 * a * c)) / (2 * a)
.
Ok. To solve those polynomials we need a view to gather 4 variables from our user, we need a controller to process the data and another view to display the result.
We'll start with the controller. Navigate to app/controllers
and create a new
controller named polynomials_controller.rb
with the following content.
# app/controllers/polynomials_controller.rb
class PolynomialsController < ApplicationController
def show
end
def new
end
def create
end
end
This enables the controller to process three types of REST requests:
- Show - A
GET
request to/polynomials/4,3,2
that displays the result - New - A
GET
request to/polynomials/new
that requests the user's input - Create - A
POST
request to/polynomials
that submits the user's input to the server
Now, let's move to the new
method and create it's view - in other words the
form that will gather the user's input. Create two new empty files at
app/views/polynomials/new.html.erb
and app/views/polynomials/show.html.erb
.
To be able to view what we did so far we need to create routes for our new
controller. Instead of using resources
as we did in the last chapter, this
time we will create all routes by hand. Open the config/routes.rb
file.
First, we will create the show
REST method, for that we need a route that
responds to GET
on /polynomials
and ends with a id like 4,3,2
or
something similar.
The Rails router allows us to create routes that correspond to HTTP GET
methods by using the get
method and passing it the name of the route we want
in addition with an indicator to the controller and method we want to call when
a request for this route comes in. Indicator have the following syntax -
controller_name#method
, so in this case our indicator would be
polynomials#show
. We also need a dynamic part in our path as we want to use
everything after polynomials/
as an id
of the polynomial. The rails router
allows us to specify dynamic parts by prefix the part's desired name with a
colon :
- e.g. polynomials/:id
.
To put it all together, our method call would be
get 'polynomials/:id', to: 'polynomials#show'
The router supports methods for all HTTP method types - get
, post
, delete
,
put
and patch
. Using them we can create the remaining two methods:
get 'polynomials/new', to: 'polynomials#new'
post 'polynomials', to: 'polynomials#create'
Just be sure to define the polynomials/new
route before the polynomials/:id
route as otherwise Rails will think that new
is an id
while visiting
polynomials/new
and would always call the show
method instead of the
new
method.
Rails.application.routes.draw do
root 'landing#index'
resources :comments
resources :posts
resources :authors
get 'polynomials/new', to: 'polynomials#new' # <-- WE ADDED
get 'polynomials/:id', to: 'polynomials#show' # <-- THESE
post 'polynomials', to: 'polynomials#create' # <-- LINES
end
Now open the following URL in your browser
http://localhost:3000/polynomials/new
and you should be greeted with a blank
screen.
Let's change this! Open up app/views/polynomials/new.html.erb
.
First let's create a large title using the <h1>
tag. There are many header
tags raging from h1
to h7
- 1 is the biggest one and 7 is the smallest.
Our header tag should look like <h1>Solve a polynomial!</h1>
.
Now we need a form! In the last chapter we saw that Rails provides shorthand methods for creating forms. We won't use them here as they're mostly useful when working with models - this will be covered in a later chapter.
To create our form we will use the form
tag and set it's action
attribute
to our create
method's route - /polynomials
, and set it's method
attribute to POST
.
<h1>Solve a polynomial!</h1>
<form action="/polynomials" method="POST">
</form>
But we are missing a way to submit our form. For this we just need to add a
button like the following <button type="submit">Solve!</button>
into the form.
<h1>Solve a polynomial!</h1>
<form action="/polynomials" method="POST">
<button type="submit">Solve!</button>
</form>
Now we need to create inputs for our variables. We can use the input
tag for
this! Every input has to have a name
attribute by which the server can read
the value, and a type
attribute which tells your browser how to render the
input. So a variable input may look like the following
<input type="number" name="a" \>
. Since we specified that the value of the
input is a number, the browser will automatically validate that the entered
value is a number and show an appropriate message if it isn't.
<h1>Solve a polynomial!</h1>
<form action="/polynomials">
<p>a * X^2 + b * X + c = d</p>
<p>
<label for="a">a:</label>
<input type="number" name="a" \>
</p>
<p>
<label for="b">b:</label>
<input type="number" name="b" \>
</p>
<p>
<label for="c">c:</label>
<input type="number" name="c" \>
</p>
<p>
<label for="d">d:</label>
<input type="number" name="d" \>
</p>
<button type="submit">Solve!</button>
</form>
But if we enter some number and press the submit button we get an error!?
Remember the two additional fields that Rails added to our form? Well, we
are missing the as we created our form by hand. Specifically, this error is
raised because we are missing the authenticity_token
input. While we could
just add it it wouldn't solve our problem as rails expects it to be set to
a predetermined random value, we simply don't know. But! We can use Rails to
add it to our form auto-magically by calling <%= form_authenticity_token %>
.
So we can add <input name="authenticity_token" type="hidden" value="<%= form_authenticity_token %>" \>
to our form to solve this issue. Here we used the hidden
field type as we
don't want the user to see it or to interact with it. We used the value
attribute to pre-set the input's value to our authenticity token.
<h1>Solve a polynomial!</h1>
<form action="/polynomials" method="POST">
<p><b>a</b> * X<sup>2</sup> + <b>b</b> * X + <b>c</b> = <b>d</b></p>
<p>
<label for="a">a:</label>
<input type="number" name="a" \>
</p>
<p>
<label for="b">b:</label>
<input type="number" name="b" \>
</p>
<p>
<label for="c">c:</label>
<input type="number" name="c" \>
</p>
<p>
<label for="d">d:</label>
<input type="number" name="d" \>
</p>
<input name="authenticity_token" type="hidden" value="<%= form_authenticity_token %>" \>
<button type="submit">Solve!</button>
</form>
And that's it! We made a form completely from scratch! If you are still
interested as to why we need the authenticity_token
input and why
it has to be a specific value you can read into it by searching for
Cross Site Request Forgery, or CSRF for short.
Let's now go back to the controller and use the data that we sent via the form.
To access data sent to the server we use the params
object which is
present in all Rails controllers. It looks and feels much like a regular hash
but has additional features that we will cover in the next chapter. For
now we can think of it and use it just like a hash.
We could implement the solution finding algorithm in the following manner:
def create
a = params[:a].to_i
b = params[:b].to_i
c = params[:c].to_i
d = params[:d].to_i
solution = solve(a, b, c, d)
solution = Marshal.dump(solution)
solution = Base64.encode64(solution)
redirect_to "/polynomials/#{solution}"
end
def solve(a, b, c, d)
require 'cmath'
a = a.to_r
b = b.to_r
c = (c - d).to_r
first = -1 * b
second = CMath.sqrt(b**2 - 4 * a * c) / (2 * a)
[first - second, first + second]
end
There are a few tricky parts here. First is the to_r
it converts any number
to a rational number this is important because second degree polynomials have
solutions with imaginary components. Then there is CMath
it allows us to
find imaginary roots of numbers, the regular Math
root method throws an
error if the input is negative. Finally there is redirect_to
which you saw
in the previous chapter. It redirects the user to the show page with the
solution. But our solution is an array of two imaginary numbers! The
browser can't understand that, therefor we use Marshall
and Base64
to convert it to a String
the browser understands. Marshall
converts Ruby
objects to a compact binary-format or in layman's terms - to ones and zeroes.
While Base64
converts the ones and zeroes to letters.
Now we have to do the same, but in reverse, in the show method to get the solutions out of the id.
def show
@solution = params[:id]
@solution = Base64.decode64(@solution)
@solution = Marshal.restore(@solution)
end
Notice that we used an instance variable. We learned in the last chapter that, using them, we can pass values from the controller into the view.
Finally we can do the show view:
<h1>The solutions!</h1>
<h2>1. <%= @solution.first %></h2>
<h2>2. <%= @solution.last %></h2>
It only prints out the first and the second solution to the problem. Nothing
to write home about. But with this we have a fully functioning polynomial
solution finder written completely by hand! This was a learning experience, but
we still have open questions! Why did Rails prefix the input's names with post
, in our original example all the way at the beginning,
and put the real name in brackets?
Ha! Rails does something very cool here, it builds a sub-hash. A form can contain many different inputs for many different contexts, not to mix them up we can put them in sub-hashes. E.g.:
<form action="/buildings" method="POST">
<p>
<label for="manager[name]">Manager name:</label>
<input type="text" name="manager[name]" \>
</p>
<p>
<label for="manager[phone]">Manager phone:</label>
<input type="text" name="manager[phone]" \>
</p>
<p>
<label for="building[address]">Building address</label>
<input type="text" name="building[address]" \>
</p>
<p>
<label for="building[name]">Building name</label>
<input type="text" name="building[name]" \>
</p>
<input name="authenticity_token" type="hidden" value="<%= form_authenticity_token %>" \>
<button type="submit">Save!</button>
</form>
Would give us the following params
object:
{
manager: {
name: "John Doe",
phone: "+112345678"
},
building: {
address: "1 Somewhere St., 00000 Somewheresville, Somewhere",
name: "ACME Corp Headquarters"
}
}
We can also store arrays in a similar manner. By using empty brackets []
we
tell Rails that we want duplicates to be handled like members of an Array
.
Otherwise if multiple fields with the same name are sent to the server only the
last will be used.
<form action="/competitions" method="POST">
<p>
<label for="participant[name][]">1st place:</label>
<input type="text" name="participant[name][]" \>
</p>
<p>
<label for="participant[name][]">2nd place:</label>
<input type="text" name="participant[name][]" \>
</p>
<p>
<label for="participant[name][]">3rd place:</label>
<input type="text" name="participant[name][]" \>
</p>
<input name="authenticity_token" type="hidden" value="<%= form_authenticity_token %>" \>
<button type="submit">Save!</button>
</form>
Gives us the following params
object:
{
participant: {
name: [
"Alice",
"Bob",
"Clay"
]
}
}
Arrays in params are most useful when working with multiple images, say in an application that has a gallery. Instead of forcing the user to upload an image one-by-one, we can allow the user to select all the images they want and upload them all at once.
But creating forms by hand is tedious, therefore Rails provides helper methods for them. Let's now rewrite our form using Rails' form helpers.
First, let's create a form. To do that we have to replace our <form>
tag with
a Rails <%= form_tag %>
. For our polynomial example the form tag would be
<%= form_tag "/polynomials", method: :post %>
. This looks quite similar to our
previous solution, but the form_tag
gets expanded to
<form accept-charset="UTF-8" action="/polynomials" method="post">
. We could
do our whole form inside that tag. E.g.:
<h1>Solve a polynomial!</h1>
<%= form_tag "/polynomials", method: :post do %>
<p><b>a</b> * X<sup>2</sup> + <b>b</b> * X + <b>c</b> = <b>d</b></p>
<p>
<label for="a">a:</label>
<input type="number" name="a" \>
</p>
<p>
<label for="b">b:</label>
<input type="number" name="b" \>
</p>
<p>
<label for="c">c:</label>
<input type="number" name="c" \>
</p>
<p>
<label for="d">d:</label>
<input type="number" name="d" \>
</p>
<input name="authenticity_token" type="hidden" value="<%= form_authenticity_token %>" \>
<button type="submit">Solve!</button>
<% end %>
But that wouldn't solve any problem or make things easier. Next, we will use the
label_tag
and number_field
to replace out HTML label
and input
tags.
Here we start to see some usefulness in using Rails helpers as they start to
provide more context and semantic value to what we are doing.
When we use a form_tag
we don't have to add an authenticity_token
input
manually, Rails does that for us.
<h1>Solve a polynomial!</h1>
<%= form_tag "/polynomials", method: :post do %>
<p><b>a</b> * X<sup>2</sup> + <b>b</b> * X + <b>c</b> = <b>d</b></p>
<p>
<%= label_tag(:a, 'a:') %>
<%= number_field nil, :a %>
</p>
<p>
<%= label_tag(:b, 'b:') %>
<%= number_field nil, :b %>
</p>
<p>
<%= label_tag(:c, 'c:') %>
<%= number_field nil, :c %>
</p>
<p>
<%= label_tag(:d, 'd:') %>
<%= number_field nil, :d %>
</p>
<button type="submit">Solve!</button>
<% end %>
Now we can combine these helpers with our knowledge from previous lectures and use a loop! That way we don't have to repeat our selfs four times to create this form.
<h1>Solve a polynomial!</h1>
<%= form_tag "/polynomials", method: :post do %>
<p><b>a</b> * X<sup>2</sup> + <b>b</b> * X + <b>c</b> = <b>d</b></p>
<% [:a, :b, :c, :d].each do |param| %>
<p>
<%= label_tag(param, "#{param}:") %>
<%= number_field nil, param %>
</p>
<% end %>
<button type="submit">Solve!</button>
<% end %>
This is now much cleaner! and it still works! But we have ways of creating forms in an even easier manner!
Now, let's add the ability to add comments to our posts! We could do use Rails' form tags and helpers to accomplish this:
<h1>Post a comment:</h1>
<%= form_tag "/comments", method: :post do %>
<%= hidden_tag(param, "#{param}:") %>
<p>
<%= label_tag(:content, "Comment:") %>
<%= text_area_tag(:content) %>
</p>
<button type="submit">Solve!</button>
<% end %>
But there is a better way. When working with models, something that will be
covered in the next chapter, we can use additional helpers which respect the
context of the model. Meaning, if we want a text_area
for the Comment's
content
attribute we could just ask the form for a text_area
and it would
infer that it's for our Comment.
To tap into this we need to change our form a little bit, instead of a
form_tag
we are going to use form_for
. Instead of an URL it accepts an
instance of a model and outputs a form builder,
like so <%= form_for Comment.new do |form_builder| %>
. Now our form is
aware of the Comment.new
context and all tags created with it will
automatically be in the context of and bear attribute names of the Comment
.
E.g.:
<%= form_for Comment.new do |f| %>
<%= f.text_area :content, size: '60x10' %>
<%= f.submit 'Post' %>
<% end %>
If we now add this snippet at the bottom of the Post's show page, we can create comments from that page.
<p id="notice"><%= notice %></p>
<p>
<strong>Author:</strong>
<%= @post.author_id %>
</p>
<p>
<strong>Content:</strong>
<%= @post.content %>
</p>
<p>
<strong>Published:</strong>
<%= @post.published %>
</p>
<p>
<strong>Comments:</strong>
</p>
<p>
<br />
<strong>Post a comment:</strong>
<%= form_for Comment.new do |f| %>
<%= f.text_area :content, size: '60x10' %>
<%= f.submit 'Post' %>
<% end %>
</p>
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
This is petty neat! If you click on post it will really create a comment and show it to you. Now we have the ability to create arbitrary inputs for our data types. But, as said, more on that in the next chapter.
Before we go on, we need to cover an even simpler method of creating forms for
models - through the help of a Gem called simple_form
. You've installed this
gem in the previous chapter and therefore don't have to do it now.
With simple form we get the best of both worlds. We can use form builders to
be aware of the Model's context while still being able to just create HTML-like
inputs and not care about the attribute type. In other words we can just use
one tag input
and it will infer the attribute type and input type form the
model we passed to it. E.g. the above example can be written like this using
simple_form:
<%= simple_form_for Comment.new do |f| %>
<%= f.input :content %>
<%= f.button :submit %>
<% end %>
Notice that we didn't specify any type at all, yet if we render this view we will see that not only did we get a text area but everything looks much nicer than the previous solution. Simple form integrates with many CSS frameworks, such as Bootstrap, which we installed in the last chapter. From that it automatically applies everything necessary for our form to seamlessly integrate with Bootstrap.
- Do all the steps in the chapter by yourself.
- In the comments creation form, on the posts show page, add a captcha.
- The captcha should consist of a simple equation with the form
x + y - z = n
, where n is randomly chosen between 100 and 999. - All fields of the captcha should be sent to the server as a sub-hash named
captcha
- The server chooses the value of
n
and stores it in a hidden field in the form - All parameters should be positive integers different from zero
- If the
x + y - z
aren't equal ton
or if any parameter doesn't conform to the specified rules, redirect the user back, usingredirect_back(fallback_location: root_path)
and display a validation error using a flash notice - If you don't know what a captcha is or why it's useful check out the Wikipedia article