Up until now we've added our ideas through the rails console
, but that's a little bit inconvenient. Instead, let's create a form to add more ideas.
The first thing we need to do is set up our routes
. What we write in routes.rb
defines how the web application is hooked up, or routed.
If we go to the terminal and type in rails routes
, we can see all of our routes that are set up. Right now, all you should see is this:
Prefix Verb URI Pattern Controller#Action
root GET / ideas#index
rails routes
rails routes
is a command that you will be using a lot. Therails routes
command displays how the application is mapped out. For example, let's take a look at the following:Prefix Verb URI Pattern Controller#Action root GET / ideas#index
This is the routing for the home directory. As we talked about before, when a user accesses the home page, the
index
method ofideas_controller.rb
is triggered.We see four different terms above:
- Prefix
- Verb
- URI Pattern
- Controller#Action
Let's go through what each of these terms mean.
Prefix
Appending
_path
to thePrefix
will generate the relative path - for exampleroot_path
will generate/
.On the other hand, you can also generate an absolute path by appending
_url
to thePrefix
. For example,root_url
will result in the full URL:https://techbrain-ideator.herokuapp.com/
URI Pattern
Notice how
root_path
resulted in creating a relative path of"/"
.If we look back to the result of running
rails routes
, we can see that theURI Pattern
is/
. When a user accesses a URL that matches theURI Pattern
, theController#Action
is triggered.Controller#Action
The
Action
(another word for method) of theController
is triggered when a user makes a request to a URL that matches theURI Pattern
.For example, when a user goes to
https://techbrain-ideator.herokuapp.com/
, the URL matches the/
URI Pattern. At this point, theindex
action of theIdeas
Controller is triggered.When a controller action is triggered, Rails will try to find an
html.erb
file with the name of the controller action inside theapps/views/controller_name
directory.For example, when the
index
action of theIdeas
Controller is triggered, Rails will try to find a filed calledindex.html.erb
inside theapps/views/ideas
directory.Verb
We won't go too deep into this topic right now, we will go over this topic in later lessons.
There are five different HTTP methods:
- GET
- POST
- DESTROY
- PUT
- PATCH
Here's what each of the methods do in short (there is a whole chapter on this later on in the course):
GET
requests are used to retrieve data. When you vist a web page, you are making aGET
request.
- When you type in
https://www.facebook.com
in your browser and press enter, you are making aGET
request to Facebook's servers.
POST
requests are used to create new objects.
- When you create a Facebook post, you are making a
POST
request to Facebook's servers
DELETE
requests are used to delete objects.
- When you delete a Facebook post, you are making a
DELETE
request to Facebook's servers
PATCH
requests are used to update objects.
- When you update a Facebook post, you are making a
PATCH
request to Facebook's servers
PUT
requests are not used anymore, usePATCH
requests instead.When a user types in a URL in the browser and presses enter, the user is making a
GET
request - the user is trying to retrieve data from the server.For example, when a user accesses
https://www.facebook.com
, the user is making aGET
request to the Facebook servers.
Let's go back to our problem. What we are trying to do right now is to create routes for the /ideas
path as a POST
request, so that we have a place to send the form data to.
Rails provides a convenient method called resources
that allows us to generate routes that are most commonly used in web applications. Let's implement this and see how it works.
Go into routes.rb
, and under root 'ideas#index'
, put resources :ideas
. Your file should look like the following:
Rails.application.routes.draw do
root 'ideas#index'
resources :ideas
end
Save the file and run rails routes
in the terminal. The output should now be this:
Prefix Verb URI Pattern Controller#Action
root GET / ideas#index
ideas GET /ideas(.:format) ideas#index
POST /ideas(.:format) ideas#create
new_idea GET /ideas/new(.:format) ideas#new
edit_idea GET /ideas/:id/edit(.:format) ideas#edit
idea GET /ideas/:id(.:format) ideas#show
PATCH /ideas/:id(.:format) ideas#update
PUT /ideas/:id(.:format) ideas#update
DELETE /ideas/:id(.:format) ideas#destroy
By putting in resources :ideas
, rails generated all of these routes for us! Notice how now we have a /ideas
path that handles POST
requests.
resources
Web applications are made up of mainly the following components:
The Index Page - the page displays all of the records
- When you go on to Facebook, it displays all of your friend's posts. This is the index page.
- This is a
GET
requestThe Show Page - the page that displays one individual record
- In the index page, all of your friend's posts are displayed. When you click on a post, you can view the individual post alone. This is the show page.
- This is a
GET
requestThe New Page - the page where you typically fill out a form to create a new record
- In Facebook, there is no New page since you can create a Facebook post from the front page. If there were a page only for creating a Facebook post, that would be the new page.
- This is a
GET
request - we aren't actually posting to the server, this page typically just displays a formThe Create Action - the backend process that creates and saves a new record to the database
- When you create a Facebook post, the create action is triggered. The create action takes care of creating the new post and saving it to the Facebook servers.
- This is a
POST
requestThe Edit Page - the page where there is typically a form where you can edit the record
- In Facebook, there is no page for just editing a Facebook post. If there was, that would be the edit page.
- This is a
GET
requestThe Update Action - the backend process that updates a record into the database
- When you edit a Facebook post and press submit, the update action is triggered.
- This is a
PATCH
request- It is also mapped to
PUT
, but it is not used vey oftenThe Destroy Action - the backend process that deletes a record from the database
- When you delete a naughty Facebook post, the destroy action is triggered and deletes the post from Facebook's server.
- This is a
DELETE
requestSince these components are so common, Ruby on Rails made it super easy to create the routes for these actions.
When you add
resources :controller_name
toroutes.rb
, it automatically creates routes for the following actions that we talked about above:
- index
- show
- new
- create
- edit
- update
- destroy
We can use these actions to create various features in our web application.
Now that we have our routing set up, we can send the form data to the /ideas
path. If we look back at the output of rails routes
, we see that a POST
request to the /ideas
path triggers the create
action inside of ideas_controller.rb
:
Prefix Verb URI Pattern Controller#Action
ideas POST /ideas(.:format) ideas#create
Right now, we don't have a create
action inside of the ideas_controller.rb
file.
Right now, when we trigger the form, the form data will be sent to the /ideas
path and trigger the create
action, but since we haven't created a create action, the form will not properly work.
We're going to use a gem
called simple_form
that makes making forms super easy for us.
What are gems?
Gems are packages of code that often times solve common problems, for instance installing
simple_form
in this case.Many common problems have been solved with gems, so it's usually a good idea to just implement the gems instead of re-implementing it yourself.
Let's go ahead and install our first gem.
In VScode, let's open up Gemfile
. You'll see a list of gems already included in the application inside this file.
The documentation can be found here. Typically, the instructions on how to install and use these gems are written inside the documentation. Let's look at the documentation and follow the instructions.
Documentation
Many tools that are developed for software engineers come with documentation. Documentation provides a manual for how to use the tool and are useful in figuring out exactly how to use the tool.
As a web developer, you will spend a lot of time reading the documentation to try to figure out how to use various tools. That's why it's super important to get used to reading documentation.
The first thing it tells us to do is to add the gem to your Gemfile
. So in your Gemfile
, add this line:
gem 'simple_form', '~> 5.3'
Next, inside our terminal, let's run the following command:
bundle install
The bundle install command
Whenever you add a new gem to the
Gemfile
, you need to install it into the application. You can install the gem into your application by running thebundle install
command. Runningbundle install
will install all of your new gems at once, and you will be using this command a lot as a Rails developer.
Every time we run bundle install, we should restart the server. Let's go back into the console where the server is running and restart it.
How to restart the server
To restart the server, first hit
Control + C
to stop the server. Then to start the server again, hit the up arrow and press enter, or type inbin/dev
again.
Awesome. Now, if we look at the documentation, we see that there are several options on how to run the simple form generator. In our case, we are going to run the generator that integrates simple form. So let's run this command.
rails generate simple_form:install
Replace the simple_form.rb
file inside config/initializers
with the content from this link. This is to implement basic tailwind classes to simple form.
Awesome. Remember, every time we run bundle install
, we should restart the server. Let's go ahead and do that.
Now that we've got simple form installed, let's go ahead and try to use it. At the top of index.html.erb
, let's add this piece of code:
<%= simple_form_for Idea.new do |f| %>
<%= f.input :description %>
<%= f.input :author %>
<%= f.submit 'Submit' %>
<% end %>
Simple form will detect what data type the form inputs should take in and generate HTML automatically.
The HTML that is generated will look something like this (some parts are abbreviated):
<form class="simple_form new_idea" id="new_idea" action="/ideas" accept-charset="UTF-8" method="post">
<input type="hidden" name="authenticity_token" autocomplete="off">
<div class="mb-4 text optional idea_description">
<label class="block text optional text-sm font-medium text-gray-600" for="idea_description">Description</label>
<textarea class="shadow appearance-none border border-gray-300 rounded w-full py-2 px-3 bg-white focus:outline-none focus:ring-0 focus:border-blue-500 text-gray-400 leading-6 transition-colors duration-200 ease-in-out text optional" name="idea[description]" id="idea_description"></textarea>
</div>
<div class="mb-4 string optional idea_author">
<label class="block string optional text-sm font-medium text-gray-600" for="idea_author">Author</label>
<input class="shadow appearance-none border border-gray-300 rounded w-full py-2 px-3 bg-white focus:outline-none focus:ring-0 focus:border-blue-500 text-gray-400 leading-6 transition-colors duration-200 ease-in-out string optional" type="text" name="idea[author]" id="idea_author">
</div>
<input type="submit" name="commit" value="Submit" data-disable-with="Submit">
</form>
Notice how textarea
was chosen for description
, since description
is a text
data type, and a string
input field was chosen for author
, since author
is a string
data type. Simple Form detects the datatypes for each form input and creates the appropriate input types.
Next, let's take a look at the first line of the form:
<form class="simple_form new_idea" id="new_idea" action="/ideas" accept-charset="UTF-8" method="post">
Notice how the action
is automatically set to "/ideas"
and the method
is set to "post"
. Simple form also automatically detects where we want to send (action
) the form and how (method
).
The action
is where the form data will be sent to when the user presses the submit button.
The method
is the HTTP method that will be used when sending the data to the server. In this case, it is specified as "post"
. As mentioned in previous lessons, POST requests are used to create new instances.
So to recap, when a user presses submit on this form, the form will be sent to /ideas
path as a POST
request.
However, since we haven't created an /ideas
path in our application, if we try to submit the form, it will just give us an error.
In order to add ideas through a form, we need to tell rails what to do when the form is submitted and tries to create an idea. What we need is a create
method which will be responsible for creating our ideas and saving them to our database.
In our ideas_controller
, let's make a create
method.
class IdeasController < ApplicationController
def index
@ideas = Idea.all
end
def create
end
end
Inside this create
action, we're going to call the create
method on Idea
.
When requests are sent in through forms, the data that is being sent looks like this:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Ew3W9K6QmcSG7HFTqhXbt0FUQOTYhG50Dm5ixVvxyeXIVm+dD1/g59ThIMI5IZ9OTh/rbaWO/7zt081/J/B1xA==", "idea"=>{"description"=>"Apples are better than pears.", "author"=>"Fruit Expert"}, "commit"=>"Submit"}
As we can see, the data being sent to the server is a hash. To review, hashes contain key value pairs. What we need to do is grab the values of the idea
key and create an Idea
object using those values.
We can access this data inside our controller with params
. We can access the ideas
with params[:ideas]
.
In other words, we are accessing the values for the idea
key in the params
hash:
params
=> {"utf8"=>"✓", "authenticity_token"=>"Ew3W9K6QmcSG7HFTqhXbt0FUQOTYhG50Dm5ixVvxyeXIVm+dD1/g59ThIMI5IZ9OTh/rbaWO/7zt081/J/B1xA==", "idea"=>{"description"=>"Apples are better than pears.", "author"=>"Fruit Expert"}, "commit"=>"Submit"}
params[:idea]
=> {"description"=>"Apples are better than pears.", "author"=>"Fruit Expert"}
As you can see, params[:idea]
returns another hash.
To access the description
and author
, we can access the value for the description
key, and access the value for the author
key:
params[:idea][:description]
=> "Apples are better than pears."
params[:idea][:author]
=> "Fruit Expert"
Now that we know how to retrieve data that is sent into the server, we can use this data to create a new Idea
and save it into the database.
Inside ideas_controller.rb
, make the file look like the following:
class IdeasController < ApplicationController
def index
@ideas = Idea.all
end
def create
@idea = Idea.create(description: params[:idea][:description], author: params[:idea][:author])
end
end
If we submit the form, you will see that it works. However, we can refactor this code.
Let's take a look at the parameters we are passing into the create
method:
description: params[:idea][:description], author: params[:idea][:author]
As we can see, we are passing in key value pairs.
When we submit a form with the description of "Apples are better than pears" and the author being "Fruit Expert", then the parameters will look like this:
description: "Apples are better than pears.", author: "Fruit Expert"
If we take a step back, this set of key-value pairs are the same as if we ran params[:idea]
.
In other words, this code:
Idea.create(params[:idea])
will run the same as this code:
Idea.create(description: params[:idea][:description], author: params[:idea][:author])
As we can see, writing Idea.create(params[:idea])
is shorter and more concise.
However, if we write Idea.create(params[:idea])
, this exposes a set of security issues. That's why this code won't work - Rails detects that there's a security issue and prevents developers from doing this.
For example, let's say we had a boolean database column called admin
for a users
table.
A hacker could try and send data to the server like such:
{"user"=>{"name"=>"Hacker :D", "admin"=>"true"}}
Then, inside our users_controller.rb
, let's say we had the following code:
class UsersController < ApplicationController
def create
User.create(params[:user])
end
end
Since User.create(params[:user])
will create a User
object based on the data passed into the server, the hacker can successfully create an admin account:
User.create(name: "Hacker :D", admin: true)
To prevent this, we need to implement strong parameters.
We can implement strong parameters by creating a method that whitelists only certain attributes.
We can do this by creating a private method called ideas_params
:
private
def idea_params
params.require(:idea).permit(:description, :author)
end
The code above whitelists description
and author
and will prevent users from sending in malicious data to the server.
The final step is to replace ideas[:params]
with idea_params
.
class IdeasController < ApplicationController
def index
@ideas = Idea.all
end
def create
@idea = Idea.create(idea_params)
end
private
def idea_params
params.require(:idea).permit(:description, :author)
end
end
When handling information in the server, you should always use strong parameters to whitelist what data can go into your application.
Awesome! Next, we need to tell rails what to do after we create the idea.
This is what we want to do:
We can redirect the user with the redirect_to
method.
redirect_to
You can redirect users to a certain page in your application with
redirect_to
.redirect_to root_path
The code above will redirect the user to
root_path
.
class IdeasController < ApplicationController
def index
@ideas = Idea.all
end
def create
@idea = Idea.create(idea_params)
redirect_to root_path
end
private
def idea_params
params.require(:idea).permit(:description, :author)
end
end
Now let's test this out. Enter an idea and an author and press 'Submit'. Voila! You should see your idea on your page, just like that!
After going through the standard git workflow, let's deploy all of the new changes that we have made to Heroku.
git push heroku master
Every time we push to Heroku, we should run migrations on the production server:
heroku run rails db:migrate
Then we should restart the server:
heroku restart