HTTP stands for Hyper Text Transfer Protocol and is the main way our computers talk to each other over the internet. In the same way that me and you talk to each other in any language (lets stick to English today) there are certain rules to our interaction. For example I can’t just walk up to you out of no where and say “goodbye”. This would be really confusing right? Well that is exactly what a protocol is, it’s a set of rules for how messages are formatted, transmitted and responded to. Think of it like proper etiquette for machines!
We have been sending out HTTP requests all the time without even realising it. Every time you go to webpage such as http://medium.com you have sent a GET HTTP request. HTTP uses different verbs or methods for different actions we want the server to do. The standard HTTP verbs are:
- GET - retrieve a resource
- POST - create a resource
- PUT / PATCH - update a resource
- DELETE - delete a resource
GET requests from the Rails Guides Blog app:
Rails.application.routes.draw do
get 'welcome/index'
get 'welcome' => 'welcome#index'
root 'welcome#index'
end
So we are now masters at the GET HTTP request! But what about all of the other verbs? Before we get into those lets first explore what a API is and a couple of other terms REST and CRUD.
The term Application Programming Interface or API for short gets thrown around a lot in programming and in many different contexts so it can be very confusing. But basically API just refers to an interface that a user can access. What?????
Well think of it this way, when we used the colorize gem to make our terminal applications have color how did we use this gem?
We used pre-defined methods to access the gems functionality. Those pre-defined methods were the colorize gems API. It was how we as developers were able to interface with it. In this digital age there are many different systems and devices we can access using their API. We could control our refrigerators, coffee machines, stereo systems and so forth.
But when we talk about a web API we are not using pre-defined methods are we?
What do we use in the web to tell a web server exactly what we want?
An HTTP request to a specific route! Thats right the urls (or routes as we will be referring to them from here on out) is the API to accessing a web server. Now that web server may send us back HTML like we are use to or it could just send us a status and some raw data that we need to handle ourselves.
It's these different routes (or endpoints as they are know in the API world) that give us the ability to talk with the web server. Now normally when we are using a web API there are certain actions that are very common. Think about blog posts what are the different actions you have been able to do with these?
- Create - make new blog posts
- Read - read a list of posts or a single post
- Update - update an existing post
- Delete - remove a post
When you are able to do all of the above we call this a complete resource or CRUD resource for short. When we are talking about web application functionality a major portion of what we want to achieve can be broken apart nicely into different CRUD resources.
Lets use this knowledge to query some different API endpoints made available to us through the internet.
Postman is a great tool we can use to make and HTTP requests. It is basically just a nice GUI for what we have done in our ruby code. Lets get comfortable using Postman because it is a great asset to web developers.
In Postman use http://api.icndb.com/jokes/random/ to send a GET request
Awesome! The data we receive is in a format called JSON. Just like any other data structure JSON is used to make data easier to consume. Looking at the above route it kind of makes sense that we would go to /jokes/random to receive a random joke but lets think about if we had 100’s of different endpoints. It could get super messy really fast! This is where REST comes in.
REST (representational state transfer) is an architectural style for how we should lay out our API. It works really well with CRUD and the different HTTP verbs. In a RESTful API the layout for a resource (in this case jokes) would look like this:
- /jokes - GET - retrieve all jokes
- /jokes - POST create a new joke
- /jokes/joke_id - GET - retrive a single joke
- /jokes/joke_id - PUT / PATCH - update a joke
- /jokes/joke_id - DELETE - delete a joke
This way we can organise our API in way that is easy to understand for each resource in our application. So far we have seen a two parts of what makes an HTTP request but there are two more important parts. Those are the:
- Headers - extra information about the request
- Body - information we want to send to the server
Another very important thing that an API can respond with is a status code. These codes have a very particular meaning. Here are some of the more common ones:
- 200 - success
- 201 - created
- 400 - bad request
- 401 - unauthorised
- 404 - not found
- 500 - internal error
For a full list of all the different status codes visit https://developer.mozilla.org/en-US/docs/Web/HTTP/Status.
Ok we are starting to understand a little better about HTTP but what is this HTTPS thing all about? The ’s’ in HTTPS stands for secure. Basically it is a way to no longer send your requests in clear text that anyone could read.
Yes we stopped those pesky eavesdroppers! The next thing we need to understand is that HTTP is a stateless protocol. Stateless means that the server does not remember anything about a prior request. Think about it as our chef has some severe memory problems, as soon as he fills your order he forgets who you were and what he was even doing. So the next time you send in a new order, you will need to provide enough information in the request so that the chef can fulfil it.
- https://www.eurovps.com/blog/what-is-hypertext-transfer-protocol/
- https://blog.codeanalogies.com/2018/02/27/web-apis-explained-by-selling-goods-from-your-farm/
- https://www.rubyguides.com/2018/08/ruby-http-request/
- https://www.youtube.com/watch?v=s7wmiS2mSXY
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
- CRUD in Sinatra
Rails.application.routes.draw do
get 'welcome/index'
get 'welcome' => 'welcome#index'
resources :articles
root 'welcome#index'
end
resources
as shorthand. We can use check what routes exist on our app:
$ rails routes
Prefix Verb URI Pattern Controller#Action
welcome_index GET /welcome/index(.:format) welcome#index
welcome GET /welcome(.:format) welcome#index
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
root GET / welcome#index
rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
To create these in the routes file we would need:
get “/articles, to: articles#index”, as: articles
post “/articles, to: articles#create”
get “/articles/new”, to: articles#new”, as: “new_articles”
get “/articles/:id/edit”, to: articles#edit”, as: “edit_articles”
get “/articles/:id”, to: articles#show”, as: articles
patch “/articles/:id”, to: articles#update”
put “/articles/:id”, to: articles#update”
delete “/articles/:id”, to: articles#destroy”
See also: https://edgeguides.rubyonrails.org/routing.html#crud-verbs-and-actions
In HTML we have a <form>
element. This element is how the browser can put together and send its own HTTP request! Remember this the same as any other HTTP request so we will need add in all the required parts of a standard HTTP requests but before we begin let’s setup a new Rails application to receive our incoming request.
Create a git repo for the movies-forms app on github and clone it locally
Create the rails app
rails new movie-forms --database=postgresql
cd movie-forms
rails db:create
rails server
Alright now that is up and running we are going to be creating an App that allows us to enter in new movies we have seen and give them a rating. First we need to start at the routes.
What routes do you think we will need?
- View all movies listed - GET "/movies"
- Create a new movie - POST "/movies"
- Form to create movie - GET "/movies/new"
- View a single movie - GET "/movies/:id"
What should we name the controller that is going to be handling these HTTP requests?
Creating the controller
rails generate controller movies
Ok now we know the basic functionality we are going to need lets setup our routes file.
config/routes.rb
Rails.application.routes.draw do
get "/movies", to: "movies#index", as: "movies"
post "/movies", to: "movies#create"
get "/movies/new", to: "movies#new", as: "new_movie"
get "/movies/:id", to: "movies#show", as: "movie"
root 'movies#index'
end
Ok the routes file is setup so lets create and fill out our controller.
app/controllers/movie_controller.rb
class MoviesController < ApplicationController
def index
end
def create
end
def new
end
def show
end
end
And lastly lets just setup some empty views for index, new and show. Create will not have a view since its responsible for creating a new movie and then redirecting to one of the other routes.
Generate the model rails generate model Movie title:string description:string rating:integer
Run the created migration rake db:migrate
Create these files
- app/views/movies/index.html.erb
- app/views/movies/new.html.erb
- app/views/movies/show.html.erb
Alright so if we go to "localhost:3000/movies/new" we don’t get an error but there isn’t anything on our screen yet. Let’s fix that by adding in some HTML.
app/views/movies/new.html.erb
<h1>Create A New Movie</h1>
<form>
</form>
Ok so we have the beginning of our HTML form which we said will be how the browser create and sends HTTP requests.
What are the different parts of an HTTP request?
- Headers
- Route
- Verb
- Body
The only part of an HTTP request that an HTML form cannot create is the headers but everything else is just fine. We will start with the route; the
element has an attribute named action. This is where we can specify which route we would like to send this HTTP request to.<form action="<%= movies_path %>" >
</form>
We are using ERB to insert the path using the route prefix instead of just hardcoding "/movies".
Why would we do this?
If that path ever changes we don’t have to go searching through our entire codebase to change it. It will automatically pull the change! This leads to way more maintainable code.
Ok so now that we have our route in place how about the verb. Well lucky for us the element has another attribute called method. This is where we can specify which type of request this will be.
<form action="<%= movies_path %>" method="GET" >
</form>
All that is left is the body of our HTTP request.
There are a bunch of HTML elements that are meant to go inside of <form>
tags. These elements can add data to the body of our HTTP request that gets generated by the form. Lets go over a few and understand how they work.
The main element that you normally see associated to a form is the <input>
element. That’s because this element is so versatile. It has two main attributes:
- type - The type of input that will be rendered into the browser window.
- name - The name is basically the key of the data in our HTTP request and whatever we place inside the element will be the value.
Here is an example
<form action="<%= movies_path %>" method="GET" >
<input type="text" name="title" />
</form>
This input tag will generate a text box that our user can type into. And when we submit our form and generate the HTTP request the body of the request would have a piece of data within it that looked like title: {value}. Because title is the name we have given this element.
Ok now lets setup our form to submit a title of a movie and its rating.
<form action="<%= movies_path %>" method="POST" >
<label>Title</label>
<input type="text" name="title" />
<label>Description</label>
<input type="textarea" name="description" />
<label>Rating</label>
<input type="number" name="rating" min="1" max="10" />
<input type="submit" value="Create" />
</form>
Nice! Ok let’s submit……dang error.
We have ran into this same error before. Remember that the authenticity token is how we protect ourselves agains CSRF (cross site reference forgery). Last time we disabled checking for the token altogether but that was because we were using Postman to hit our API. Since we are using the same domain that the API is on (localhost) we can just include the token into our form and we are good to go. I like to put these kinds of fields at the top.
Rails makes this very easy for us. Just add the below code to your form anywhere before the submit input.
<form action="<%= movies_path %>" method="POST" >
<input type="hidden" value="<%= form_authenticity_token %>" name="authenticity_token" />
<label>Title</label>
<input type="text" name="title" />
<label>Description</label>
<input type="textarea" name="description" />
<label>Rating</label>
<input type="number" name="rating" min="1" max="10" />
<input type="submit" value="Create" />
</form>
We won’t see this field in our form because it is a hidden field. Now when we click submit we are no longer getting an error but nothing seems to be happening. That’s because we haven’t added any logic to our create method in the controller. Lets first print out our params variable and make sure we are receiving data first.
def create
puts params
end
Look at the server output to see the data is coming through. Let’s fill out our controller methods.
class MoviesController < ApplicationController
def index
@movies = Movie.all
end
def create
@movie = Movie.new(movie_params)
if @movie.save
redirect_to @movie
else
render 'new'
end
end
def new
@movie = Movie.new
end
def show
@movie = Movie.find(params[:id])
end
private
def movie_params
params.permit(:title, :description, :rating)
end
end
And now our other views.
app/views/movies/index.html.erb
<h1>Movies</h1>
<ul>
<% @movies.each_with_index do |movie, index| %>
<li>
<%= movie.title %> - <%= movie.rating %> <%= link_to "show", movie_path(index + 1) %>
</li>
<% end %>
</ul>
app/views/movies/show.html.erb
<h2><%= @movie["title"] %></h2>
<h3>Rating</h3>
<p><%= @movie["rating"] %></p>
<%= link_to "Back", :back %>
Awesome now all that’s left is to add in a way to update.
Ok first we need to setup our routes, controller methods and a view with a form to update our movie.
config/routes.rb
put "/movies/:id", to: "movies#update"
patch "/movies/:id", to: "movies#update"
get "/movies/:id/edit", to: "movies#edit", as: "edit_movie"
app/controllers/movies_controller.rb
def edit
@movie = Movie.find(params[:id])
end
def update
@movie = Movie.find(params[:id])
if @movie.update(movie_params)
redirect_to @movie
else
render 'edit'
end
end
app/views/movies/edit.html.erb
<h1>Edit Movie</h1>
<form action="<%= movie_path(params[:id]) %>" method="PUT" >
<input type="hidden" value="<%= form_authenticity_token %>" name="authenticity_token" />
<label>Title</label>
<input type="text" name="title" value="<%= @movie.title %>" />
<label>Description</label>
<input type="textarea" name="description" value="<%= @movie.description %>" />
<label>Rating</label>
<input type="number" name="rating" min="1" max="10" value="<%= @movie.rating %>" />
<input type="submit" value="Update" />
</form>
Ok so now when we click send……wait why did it send a GET request? Didn’t we say PUT? Well back in the day HTTP requests only had two verbs GET and POST. And our browser still only knows how to use these two. But then how do we use PUT, PATCH and DELETE? Well in Rails we will change our form method to be POST and add in a hidden input to the form with the actual method we would like to use.
<h1>Edit Movie</h1>
<form action="<%= movie_path(params[:id]) %>" method="POST" >
<input type="hidden" value="<%= form_authenticity_token %>" name="authenticity_token" />
<input type="hidden" name="_method" value="patch" />
<label>Title</label>
<input type="text" name="title" value="<%= @movie.title %>" />
<label>Description</label>
<input type="textarea" name="description" value="<%= @movie.description %>" />
<label>Rating</label>
<input type="number" name="rating" min="1" max="10" value="<%= @movie.rating %>" />
<input type="submit" value="Update" />
</form>
It’s working!
The different types and <input>
element can be are:
- button
- checkbox - another attribute "checked"
- color
- date
- datetime-local
- file
- hidden
- image
- month
- number
- password
- radio
- range
- reset
- search
- submit - creates the HTTP request and sends it
- tel
- text
- time
- url
- week
Play around with the different types and see what they generate on the screen.
Two other elements that can help form the body of an HTTP request within a < form > tag are:
textarea - large text input, made for typing in big pieces of text.
select - a dropdown
option - a child of < select> and forms the different options available in the dropdown
- Add an Edit link to the Show page
- Add a Create link to the movie list page
- Add code to seeds.rb that adds 20 movies
- use Faker
- do not include a destroy_all
- Add a destroy method
- use the tutorial https://guides.rubyonrails.org/getting_started.html as a guide
- Add a link to Delete book on the movie list page
- Add styling
- Update the readme.md