| Objective |
|---|
| We should grasp the concepts related to routing requests, connecting them to controllers, present a view, and gain a naive understanding of the flow of a request and response. |
- Conventions over Configurations
- Models, Views, and Controllers
- HTTP (GET, POST, PUT, Delete) verbs
- The
paramsof a request, (Covered in Sinatra) - Forms (action, method, and form fields)
- redirects
| Motive: |
|---|
| Familiarize ourselves with the initial setup of a new application with the intent of making a planes application |
$ rails new route_app$ cd route_app$ rails s
Now our app is up and running, localhost:3000. At our rootroute we notice a "Welcome aboard message". That's because we have yet to create a controller and views that we can set as our root.
Go to config/routes.rb and inside the routes block erase all the commented text. Now we can define all our routes.
Let's get ready for our first route.
-
The nature of any route goes as follows:
request_type '/for/some/path/goes', to: "controller#method"e.g. if we had a
PuppiesControllerthat had aindexmethod we could sayget "/puppies", to: "puppies#index" -
Using the above routing pattern we'll write our first
/config/routes.rbRouteApp::Application.routes.draw do get '/', to: "planes#index" endWe'll have to define a
planescontroller and aindexmethod soon enough. -
However, we distinguish the
'/'(root) from other routes, and we just writeroot to: 'planes#show'to indicate it inroutes.rb./config/routes.rbRouteApp::Application.routes.draw do root to: 'planes#index' end
We want to create a planes_controller. NOTE: Controller names are always plural and files should always be snake_case.
$ subl app/controllers/planes_controller.rb
Let's begin with the following
class PlanesController < ApplicationController
def index
render text: "Hello, pilots."
end
end
We have defined thePlanesController class, given it the method #index, and told the #index to render a text response 'Hello, pilots.'
Note: We've also indicated that
PlanesControllerinherits fromApplicationController, which looks like the following:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
Indeed,
ApplicationControlleralso inherits fromActionController::Base, which is just the main action handling class. Actions it might handle are requests, responses, rendering views, etc. TheApplicationControllerhelps define the setup/configuration of all other controllers and has methods defined accross the entire application.
If we go to localhost:3000/ we get the greeting.
Let's seperate our rendered greeting into a view called index.html.erb, which by default ActionController will look for in a app/views/planes/ folder. Create the file below
app/views/planes/index.html.erb
Hello, pilots!
and make the following changes to PlanesController.
app/controllers/planes_controller.rb
PlanesController < ApplicationController
def index
# Note it used to say
# render text: 'Hello, pilots'
render :index
end
end
A model is just a representation of a SQL table in our database, and the communication between the two is handled by rails via ActiveRecord, which has a list of prestored SQL commands to facilitate communication.
In terminal, we create our plane model using a rails generator as follows,
$ rails g model plane name type description
which just creates the instructions in our app to tell SQL to create our model. To actually create this table data in our SQL database we do a migration. To migrate our database we use rake as follows:
$ rake db:migrate
Now our application will have access to a model called Plane that will be persitent.
NOTE: DON'T SKIP THIS STEP
We go straight into terminal to enter rails console.
$ rails console
The command above enters the rails console to play with your application.
To create our first plane model in our database we use our reference the Plane class and call the Plane#create method to write our plane to our database.
> Plane.create({name: "x-wing", type: "unknown", description: "top secret"})
=> #<Plane ....>
This will avoid issues later with index trying to render planes that aren't there.
| Motive |
|---|
| We've seen a little of each part of the MVC framework, and now we cycle back through over and over as we develop an understanding of the work flow. |
We don't have any planes in our database yet. To be able to make planes we must create a route for them. The RESTful convention would be to make a form available at /planes/new.
Let's add this route.
/config/routes.rb
RouteApp::Application.routes.draw do
root to: 'planes#index'
# just to be RESTful
get '/planes', to: 'planes#index'
# it's a `get` because
# someone is requesting
# a page with a form
get '/planes/new', to: 'planes#new'
end
The request for /planes/new will search for a planes#new, so we must create a method to handle this request. This will render the new.html.erb in the app/views/planes folder.
app/controllers/planes_controller.rb
PlanesController < ApplicationController
...
def new
render :new
end
end
Let's create the app/views/planes/new.html.erb with a form that the user can use to sumbit new planes to the application. Note: the action is /planes because it's the collection we are submiting to, and the method is post because we want to create.
app/views/planes/new.html.erb
<form action="/planes" method="post">
<input type="text" name="plane[name]">
<input type="text" name="plane[type]">
<textarea name="plane[description]"></textarea>
<button> Save Plane </button>
</form>
Note: how we have now defined our next route, which is
post "/planes", to: "planes#create"
Our sumbission of the plane form in new.html.erb isn't being routed at the moment let's change that
/config/routes.rb
RouteApp::Application.routes.draw do
root to: 'planes#index'
get '/planes', to: 'planes#index'
get '/planes/new', to: 'planes#new'
# handle the submitted form
post '/planes', to: 'planes#create'
end
This leads to the most complicated method yet to be talked about. For now we will just make it redirect to the "/planes" route.
app/controllers/planes_controller.rb
PlanesController < ApplicationController
...
def create
redirect_to "/planes"
end
end
Someone should now be able to submit a form to our site, right???
It turns out that forms aren't so simple in Rails anymore. There are security concerns that rails is trying to handle from the very beginning.
Recall that we indicated that
PlanesControllerinherits fromApplicationController, which looks like the following:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
The line that says
protect_from_forgery with: :exceptionmeans that our forms will have added layer of security.
To even get our rails app to accept our form it needs an authenticity_token, which we will casually add in and explain later.
app/views/planes/new.html.erb
<form action="/planes" method="post">
<input type="text" name="plane[name]">
<input type="text" name="plane[type]">
<textarea name="plane[description]"></textarea>
<%= token_tag form_authenticity_token %>
<button> Save Plane </button>
</form>
Our form should now submit properly. However, we will see that rails makes handling all the things required in a form easier using something called form helpers later.
We just need to save the data being sent in the request. We might be tempted to do the following.
app/controllers/planes_controller.rb
PlanesController < ApplicationController
...
def create
plane = params[:plane]
Plane.create(plane)
redirect_to "/planes"
end
end
However, while this might be fine in rails 3.2 it won't fly in rails 4.0, which has something called strong parameters. To follow this strong parameters convention we must change the way we accept params to something like one of the following.
We can grab the :plane hash out of the params hash, and the tell it to permit the keys we want: :name, :type, and :description.
app/controllers/planes_controller.rb
PlanesController < ApplicationController
...
def create
plane = params[:plane].permit(:name, :type, :description)
Plane.create(plane)
redirect_to "/planes"
end
end
or, (preferably) just say .require(:plane)
app/controllers/planes_controller.rb
PlanesController < ApplicationController
...
def create
plane = params.require(:plane).permit(:name, :type, :description)
Plane.create(plane)
redirect_to "/planes"
end
end
In reality strong params is just a nice way of making sure someone isn't setting param values that you don't want them to be setting.
We first need to setup our #index method in planes
app/controllers/planes_controller.rb
PlanesController < ApplicationController
def index
@planes = Plane.all
render :index
end
...
end
Let's finally put some erb in our index view.
app/views/index.html.erb
<% @planes.each do |plane| %>
<div>
Name: <%= plane[:name] %> <br>
Type: <%= plane[:type] %> <br>
Description: <%= plane[:description] %>
</div>
<% end %>
We've successfully made an index, new, and create. Let's talk about adding show, edit, and update
Right now, our app redirects to #index after a create, which isn't helpful for quickly verifying what you just created. To do this we create a #show.
Let's add our show route.
/config/routes.rb
RouteApp::Application.routes.draw do
root to: 'planes#index'
## My new show method
get '/planes/:id', to: 'planes#show'
get '/planes', to: 'planes#index'
get '/planes/new', to: 'planes#new'
post '/planes', to: 'planes#create'
end
Is this right?? No, our /planes/:id path is above our /planes/new path, which means it will never get checked, and all requests for /planes/new.
It should be as follows
/config/routes.rb
RouteApp::Application.routes.draw do
root to: 'planes#index'
get '/planes', to: 'planes#index'
get '/planes/new', to: 'planes#new'
## My new show method
get '/planes/:id', to: 'planes#show'
post '/planes', to: 'planes#create'
end
A controller method
app/controllers/planes_controller.rb
PlanesController < ApplicationController
...
def show
plane_id = params[:id]
@plane = Plane.find(plane_id)
render :show
end
end
A view for showing a plane
app/views/show.html.erb
<div>
Name: <%= @plane.name %> <br>
Type: <%= @plane.type %> <br>
Description: <%= @plane.description %>
</div>
The #create method redirects to #index (the /planes path), but this isn't very helpful for verrifying that a newly created plane was properly created. The best way to fix this is to have it redirect to #show.
PlanesController < ApplicationController
...
def create
plane = params.require(:plane).permit(:name, :type, :description)
plane = Plane.create(plane)
redirect_to "/planes/#{plane.id}"
end
end
Recall that the #show method has a path /planes/:id, which means we need to specify the id of the plane we want to show. To do this we added
plane = Plane.create(plane)
and once it's created it has an id we can use to redirect. We use string interpolation as follows.
redirect_to "/planes/#{plane.id}"
Editing a plane model requires two seperate methods. One to display the model information to be edited by the client, and another to handle updates submitted by the client.
If look back at how we handled the getting of our new form we see the following pattern.
- Make a route first
- Define a controller method
- render view
The only difference is that now we need to use the id of the object to be edited. We get the following battle plan.
- Make a route first
- Make sure it specifies the
idof the thing to be edited
- Make sure it specifies the
- Define a controller method
- Retrieve the
idof the model to be edited fromparams - use the
idto find the model
- Retrieve the
- render view
- use model to display in the form
We begin with handling the request from a client for an edit page.
-
We can easily define a RESTful route to handle getting the edit page as follows
/config/routes.rbRouteApp::Application.routes.draw do root to: 'planes#index' get '/planes', to: 'planes#index' get '/planes/new', to: 'planes#new' get '/planes/:id', to: 'planes#show' # The Edit path get '/planes/:id/edit, to: 'planes#edit' post '/planes', to: 'planes#create' end -
Similarly, using our
#showmethod as inspiration we write an#editmethodapp/controllers/planes_controller.rbPlanesController < ApplicationController ... def edit plane_id = params[:id] @plane = Plane.find(plane_id) render :edit end end -
Let's quickly begin the setup of an
editform using ournew.html.erbfrom earlier, by just adding...value="<%= @plane.attr_name %>"...to each respect field.app/views/planes/edit.html.erb<form action="/planes" method="post"> <input type="text" name="plane[name]" value="<%= @plane.name %>"> <input type="text" name="plane[type]" value="<%= @plane.type %>"> <textarea name="plane[description]" value="<%= @plane.description %>"></textarea> <%= token_tag form_authenticity_token %> <button> Update Plane </button> </form> -
Next we have to modify the
actionandmethod, but we can't explicitly change the method at the top of the form, because the browser still needs to send aPOSTrequest. Instead we add field that contains the method we actually want to make,PUT. Using something as follows<input name="_method" type="hidden" value="put" />Combined with the change we make to the
actionwe get the following.app/views/planes/edit.html.erb<form action="/planes/<%= @plane.id %>" method="post"> <input name="_method" type="hidden" value="put" /> .... </form>
That's pretty much the whole-shebang when comes to getting an edit page. Our previous knowledge has really come to help us understand what we need to do. We'll see this also true for the update that still needs to be handled witht the submission of the form above.
If look back at how we handled the submission of our new form we see the following pattern.
- Make a route first
- Define a controller method
- redirect to something
The only difference now is that we will need to use the id of the object being update.
- Make a route first
- Make sure it specifies the
idof the thing to be updated
- Make sure it specifies the
- Define a controller method
- Retrieve the
idof the model to be updated fromparams - use the
idto find the model - retrieve the updated info sent from the form in
params - update the model
- Retrieve the
- redirect to show
- use
idto redirect to#show
- use
-
Make a route that uses the
idof the object to be updated/config/routes.rbRouteApp::Application.routes.draw do root to: 'planes#index' get '/planes', to: 'planes#index' get '/planes/new', to: 'planes#new' get '/planes/:id', to: 'planes#show' get '/planes/:id/edit, to: 'planes#edit' post '/planes', to: 'planes#create' # Route the incoming update using the id put '/planes/:id', to 'planes#update' endNote the method we now need to create is called
#update -
In the
PlanesControllerwe will create the#updatemethod mentioned aboveapp/controllers/planes_controller.rbPlanesController < ApplicationController ... def update plane_id = params[:id] plane = Plane.find(plane_id) # get updated data updated_attributes = params.require(:plane).permit(:name, :type, :description) # update the plane plane.update_attributes(updated_attributes) #redirect to show redirect_to "/posts/#{plane_id}" end end