class: middle, center # Routing, Controllers, Views & REST [http://pjb3.me/bewd-routing](http://pjb3.me/bewd-routing) .footnote[ created with [remark](http://github.com/gnab/remark) ] --- # Routing For each incoming request, the router determines the appropriate controller action to handle the request For example, if an HTTP request looks like this: .terminal GET /orders If would match a route like this: ```ruby get '/orders' => 'orders#index' ``` This means if the HTTP method of the request is a GET, which it is, and the path is `/orders`, which it is, then the request should be handled by instantiating the `OrdersController` and calling the `index` method. ```ruby class OrdersController < ApplicationController def index # Do Something end end ``` --- # Matching Routes Routes are defined in the `config/routes.rb` file in a specific order. The routers checks each route in order until it finds one that matches. Once if finds a match, it stops checking routes, instantiates the controller and calls the method that matches the action. If the routes look like this: ```ruby get '/subscriptions' => 'subscriptions#index' post '/orders' => 'orders#create' get '/orders' => 'orders#index' root :to => 'site#index' ``` If the request is an HTTP GET with the path of `/orders`, the router does the following steps: 1. Is the request a GET? Yes, then is the path `/subscriptions`? No, try the next route 2. Is the request a POST? No, try the next route 3. Is the request a GET? Yes, then is the path `/subscriptions`? Yes, instantiate `OrdersController` and call `index` The last route isn't checked Multiple routes could match a request, the first match is the one that is used The root route is the route used when the path is blank. --- # Exercise Make sure you have your rails server running by running the command `rails server` from a bash shell in the betastore directory. Go to [http://localhost:3000/products](http://localhost:3000/products) in your browser. Do you get an error message? What is the error message? Why do you get this error message? --- # Exercise Add a route to `config/routes.rb` so that when you make a GET request to `/products`, the `index` action of the products controller is called. Now what happens when you go to [http://localhost:3000/products](http://localhost:3000/products)? Do you get a different error message? What does this error mean? --- # Controllers Controllers use information from the request, such as the parameters, cookies, headers, etc., to create, retrieve, update or delete data via models and then pass data to the view so an HTML page can be rendered. Let's create a controller. Controllers go in `app/controllers` in a file that matches the name of the controller. For example, to create a controller for products, create a file at `app/controllers/products_controller.rb` that looks like this: ```ruby class ProductsController < ApplicationController end ``` Now try to go to [http://localhost:3000/products](http://localhost:3000/products). What error do you get now? --- # Actions All public methods of controllers are eligible to be actions, but they can only be called if there is at least one route that exposes the action. Add an empty `index` action to the products controller that looks like this: ```ruby class ProductsController < ApplicationController def index end end ``` Now try to go to [http://localhost:3000/products](http://localhost:3000/products). What error do you get now? --- # Views Views are the HTML templates used to construct the actual HTML that will be returned to the browser. Views go in `app/views` and then in a directory that matches the controller name. In that directory there is one view template per action. Views are written in a templating language called **ERB** that we will cover in more detail later. You can put just HTML into an ERB template, which is what we will do now. To create a view for the products index action, create a file named `app/views/products/index.html.erb`. The `products` directory will not exist in `app/views` yet, so you will need to create it. Put the following code into the file: ```html
This is a list of products
``` Now try to go to [http://localhost:3000/products](http://localhost:3000/products). You should see the HTML from the index.html.erb rendered by your browser. --- # What we have so far 1. Browser makes an HTTP request 2. Router determines with controller + action should handle the request 3. Router calls the action of the controller 4. Controller renders the view template that matches the name of the action 5. HTML generated by the view is returned to the browser 6. Browser renders the HTML returned by the response --- # Making Our View Dynamic Our products page says it is supposed to be a list of products, but it is not listing any products. Let's fix that. In the `app/views/products/index.html.erb` that you created, make it look like this: ```rhtml
This is a list of products
<% for product in %w[Hat Hoodie Journal] %>
<%= product %>
<% end %>
``` The dynamic parts of the template are the parts between the `<% %>` tags and the `<%= %>` tags. The language between these tags is just Ruby. You can put any valid Ruby code in the tags. There are a lot of methods that Rails makes available for you to call when using these tags and we will explore them later. Code between the `<% %>` is just evaluated, it doesn't result directly in any code being added to the HTML. This makes sense in this case for the for loop because it doesn't return a value that we want in the output. Code between the `<%= %>` is evaluated and the result is included in the output. We use that in this case inside the for loop to print out the name of each product. When you view [http://localhost:3000/products](http://localhost:3000/products) now you should see a list of products in the output. --- # Separating Data From Presentation One of the important techniques for keeping a Rails application well organized and easy to refactor is to keep all of the code that loads up the data in the controller and out of the views. The job of the controller is to load up data and the job of the view is to just render the HTML based on the data. The most common way to load data in the controller and make it available in the view is to use instance variables. Modify the index action of the products controller to look like this: ```ruby def index @product_names = %w[Hat Hoodie Journal] end ``` Update the view to use the instance variable: ```rhtml
This is a list of products
<% for product in @product_names %>
<%= product %>
<% end %>
``` In this case the product names is very simple, so the benefit of this separation may not be immediately obvious, but as our application gets more complicated, the benefit will be more clear. The rule to follow is to **load data in the controller into instance variables and then access those instance variables from the view**. --- # Using Models Your data is not typically going to be hard-coded into the controller. Instead, the data will be in the database and you access it via the models. Modify your index action to look like this: ```ruby def index @products = Product.order('name').all end ``` We have renamed the instance variable to products, because it is now an Array of Product models instead of an Array of Strings that are product names. We therefore have to update the view to look like this: ```rhtml
This is a list of products
<% for product in @products %>
<%= product.name %>
<% end %>
``` We now have a page that a list of products dynamically built from whatever products we have in the database. --- # Exercise 1. Use the `rails console` to create a new Product. Refresh the products page in your browser. Does the new product show up? 2. Use the `rails console` to rename one of the Products. Refresh the products page in your browser. Does the updated name show up? 3. Use the `rails console` to delete one of the Products. Refresh the products page in your browser. Is the product no longer there? --- # Namespacing Routes Many Rails application will have different areas that are access by different people and look completely different. A good example of this is in our Betastore, we'd like to have the main website which customers can browse our list of products and order things and then a "back-end" admin site that can be used by the Betastore staff to add new products and fulfill orders as the come in. To facilitate this, we can use a separate namespace for the admin controllers in our application. Add this to your routes: ```ruby namespace :admin do get '/products' => 'products#index' end ``` Now that our routes a getting a bit more complicated, it is helpful to use the `rake routes` command to see what they look like: .terminal $ rake routes Prefix Verb URI Pattern Controller#Action admin_products GET /admin/products(.:format) admin/products#index products GET /products(.:format) products#index We can see that we have one controller `admin/products` and another controller that is just `products`. These are two independent controllers that can do completely different things if they so choose. --- # Namespacing Controllers To create a `admin/products` controller, we have to use Ruby's modules to provide the namespacing. Create a folder in `app/controller` named `admin`. Then in that directory, create a file `products_controller.rb` that looks like this: ```ruby class Admin::ProductsController < ApplicationController def index @products = Product.order('name') end end ``` Notice the `::` in the name of the controller. That is how namespacing is done in Ruby. Namespacing is just a fancy word for organizing things. In this case, we want to have 2 different product controllers, one for the customer facing website and one for the internal staff. We can't name them both products controller, so by namespacing the admin one, we have a way to differentiate them. At this point, the 2 controllers look the same, but they will start to be different as we add more functionality. Right now, if you go to [http://localhost:3000/admin/products](http://localhost:3000/admin/products), you should get an error about a missing template. We know how to fix that, so we'll do that in the next slide. --- # Namespaced Views To create a view for a namespaced controller, you just have to put the template in the directory that matches the controller namespace. So in the `app/views` directory, create a folder named `admin`, which matches the name of the namespace. In that directory, create a folder named `products`, which matches the part of the name of the controller under the namespace. Then create a file named `index.html.erb` in that directory that looks like this: ```rhtml
Products
ID
Name
Price
Created At
<% for product in @products %>
<%= product.id %>
<%= product.name %>
<%= product.price %>
<%= product.created_at %>
<% end %>
``` Now if you go to [http://localhost:3000/admin/products](http://localhost:3000/admin/products), you should see a table with the products in it. --- # Variables in routes paths You can define a route which has a placeholder for any value. Put this into the admin namespace in `config/routes.rb`: ```ruby get '/products/:id' => 'products#show' ``` The means that if the request has a URL that starts with `/products/` followed by anything, it will match. For example, `/products/1` matches, so does `/products/hello`. The value that matches the id part of the route, which are `"1"` and `"hello"` in this case, are stored in a parameter that the controller will be able to access. FYI, `/products` does not match this route, --- # Parameters In the controller, you can access an parameters that are passed in via the `params` Hash, like this: ```ruby def show params[:id] end ``` Params can come from the routes, as discussed, in the previous slide, or from query string params or as we will see later, form fields. --- # Exercise 1. Modify the admin products show action to get the product that matches the id param from the database and store it in an instance variable named `@product` 2. Modify the admin products show view to display the ID, name, price, image_url and created_at for the product. --- # Named Routes In the routes, you can give a name to the route. This will allow you generate the route using a method from your controllers and views. Change your admin products show route to look like this: ```ruby get '/products/:id' => 'products#show', as: 'product' ``` The value of the `as` parameter is the name of the route. To generate that route, modify the view for the admin products index by replacing where we are printing the id of the product, which looks like this: ```rhtml <%= product.id %> ``` With a call to the route, which looks like this: ```rhtml <%= admin_product_path(product) %> ``` The name of the method is whatever you named the route with `_path` added to the end. Because the route is in the admin namespace, `admin_` is added to the front. The route path has one variable in it, so we pass in the product to be used as the value for the path. Rails knows to called `id` on the active record object to get the actual value that should be included in the path. The actual path will look like this for the first product: ``` /admin/products/1 ``` --- # View Helpers Rails makes methods available for you to use in the views. These are typically called view helpers. Rails has many that are built in and as we will see later, you can add your own. One of the most commonly used view helpers is called `link_to` and it is used to generate links. ```rhtml <%= link_to product.id, admin_product_path(product) %> ``` The way the `link_to` method works is the first argument in the URL or path that the link should go to and the second argument is what text should appear. If you inspect the HTML of the page, you will see something like this: ```html
1
``` If you click on on the link, you should then see your admin product show action rendered, which at this point just prints out params. --- # Exercise Make the product names displayed on the admin products index be links to the admin product show using the link_to helper and the named route. --- # Number Formatting Helpers Rails has several built in helpers for formatting different types of numbers for display. For example, if a number represents a currency, like our price does, you are going to want to display it with a dollar sign, with commas and 2 decimal places. ```rhtml <%= number_to_currency product.price %> ``` You can read about some other types of number formatting, such as percentages, file sizes, etc. here: [http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html](http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html) ## Exercise Change the both places where were are printing out the price, in the admin products index view and the admin products show view, to display the price like this: --- # Images Right now the admin product show is just displaying the product image URL. If we would like to display the actual image, we can use the `image_tag` helper, like this: ```rhtml <%= image_tag @product.image_url, alt: @product.image_url %> ``` ## Exercise Modify the admin product show view to use this helper. --- # Formatting Date/Times If you want to print a time in a better format, one option Rails gives you is a helper called `time_ago_in_words`. Change where created_at is displayed in the admin product show view to use this helper: ```rhtml <%= time_ago_in_words product.created_at %> ago ``` You can see that this displays a human readable time like "about 30 minutes". For more precise formatting of a time, you can use the Ruby `strftime` method, like this: ```rhtml <%= product.created_at.strftime('%m/%d/%Y') %> ``` Read the documentation for `Time#strftime` here: [http://ruby-doc.org/core-2.1.1/Time.html#method-i-strftime](http://ruby-doc.org/core-2.1.1/Time.html#method-i-strftime) ## Exercise Modify the code in the view to display both how long ago the product was created and the time it was created in the format "Tuesday, March 25, 2014 8:00 PM UTC" --- # Forms Now that we've gotten the hang of routes, controllers, actions and views, let's move on to creating forms to allow us to create records in the database. The first thing we'll need is an action to display the form to create a new product. 1. Add a route to the admin namespace so that the path `/products/new` goes to the new action of the admin products controller. Name the route `new_product`. 2. Create a view template for the admin products new action. Put some basic HTML in it like an **h1** that says New Product. Did you get it to work? Did you notice that you didn't have to create a new action in the controller? That is because in Rails if the action doesn't do anything, you don't actually need to create the blank action, you can just create a view and it will get rendered. That's a neat trick and it's good to know that is how it works, but it turns out that we actually do need our new action to do something, so create one that looks like this: ```ruby def new @product = Product.new end ``` --- # Form Builders Now we have an empty Product object that our view can make use of. Add this code to the admin products new view: ```rhtml <%= form_for [:admin, @product] do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.submit %> <% end %> ``` This view will correctly, but try to fill something in and submit it. What error do you get? --- # Route for create When we submit a form, there needs to be an action to handle it. Make a route like this: ```ruby post '/products' => 'products#create' ``` The default method for forms is post, so this is the first time we have defined a route using post. Notice that it is the same path as the index, but a post instead of a get. --- # create action Let's create an action to save the data we received from the form into a new record in the products table. ```ruby def create @product = Product.new(params.require(:product).permit!) if @product.save redirect_to admin_products_path, notice: "Product #{@product.id} was created" else render 'new' end end ``` In this code, if we are able to save the product, then we'll redirect to the products listing with a flash message letting the user know that it worked. If the product could not be saved, most likely due to a validation error, then we will re-render the form and display the errors A flash message is a message that temporarily exists that is typically used to let the user know that something happened. You can print it out in a view like this: ```rhtml <% if flash[:notice].present? %>
<%= flash[:notice] %>
<% end %> ``` The flash will be printed on the page after the flash is set, not the current page. --- # Exercises 1. Modify the admin products index view to display the `flash[:notice]` message if it exists 2. Modify the admin products new view to display the errors on the product if there are any. Make sure to add at least one validation rule to product so that there is a way to make it fail to save. 3. Add more fields to the form so you can enter the price and image_url for the product. --- # RESTful Design Representational State Transfer (REST) is a design pattern that states that you should design of your web application as an application that exposes resources to its clients in the form of URLs and allows those clients to interact with those resources using standard the HTTP verbs: * **GET** - Retrieve the current state of the resource * **POST** - Create a new resource * **PUT** - Replace a resource * **PATCH** - Partially update a resource * **DELETE** - Remove a resource Rails fits this pattern in spirit, if not by strict detail, therefore this Rails apps are often referred to as RESTful --- # Types of Resources In Rails applications, the two main types of resources are **collections** and **members**. One controller typically handles all actions related to a collection resource and all members of that collection. The 7 routes for a RESTful controller typically are: ```ruby get '/products' => 'products#index', as: 'products' post '/products' => 'products#create' get '/products/new' => 'products#new', as: 'new_product' get '/products/:id/edit' => 'products#edit', as: 'edit_product' get '/products/:id' => 'products#show', as: 'product' patch '/products/:id' => 'products#update' delete '/products/:id' => 'products#destroy ``` Previous to Rails 4, `put` was commonly used for update --- # Role of RESTful Actions .terminal index - Show the list of all products, link to new product show - Show the details for a product, links to edit and delete product new - Show the form to create a new product create - Create the new product edit - Show the form to edit an existing product update - Update an existing product destroy - Remove an existing product Not every controller will allow for all of these actions, but most actions will map to one of these actions If you are naming an action anything other than `index`, `show`, `new`, `create`, `edit`, `update` or `destroy`, you should consider if you could alter your design so that you could use one of the standard names Adhering to this pattern as much as possible is a very useful design tool --- # resources Have you noticed we've been doing a lot of typing for these routes? Well it turns out that since this pattern is baked into rails, there is support for automatically generating all of these routes. First, for our products resource, the full routes look like this: ```ruby get '/products' => 'products#index', as: 'products' post '/products' => 'products#create' get '/products/new' => 'products#new', as: 'new_product' get '/products/:id/edit' => 'products#edit', as: 'edit_product' get '/products/:id' => 'products#show', as: 'product' patch '/products/:id' => 'products#update' delete '/products/:id' => 'products#destroy ``` But we can abbreviate that to just: ```ruby resources :products ``` That's a lot shorter and a lot more typo-proof as well. After you make this change, run `rake routes` to see what the routes look like. --- # Exercise Implement the full set of RESTful actions for the admin products controller, so we can create, update and delete products.