Skip to content

Instantly share code, notes, and snippets.

@leahgarrett
Last active April 16, 2019 02:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leahgarrett/d75aa2ee052b671a094bdc0137303b00 to your computer and use it in GitHub Desktop.
Save leahgarrett/d75aa2ee052b671a094bdc0137303b00 to your computer and use it in GitHub Desktop.
HTTP, routing and CRUD

HTTP Requests

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.


APIs, CRUD and REST

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.

Resources


Rails Routing

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


Forms The HTML Way - movies-forms app

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?

MoviesController

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.


Movie Model

Generate the model rails generate model Movie title:string description:string rating:integer

Run the created migration rake db:migrate


Movie Views

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.


Form Inputs

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.


Authenticity Token

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

MovieController

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.


New HTTP Verbs

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!


Next Steps

The different types and <input> element can be are:

  • button
  • checkbox - another attribute "checked"
  • color
  • date
  • datetime-local
  • email
  • 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
  • Add styling
  • Update the readme.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment