Rails Applications Without Scaffold (part 1: Show and List)

Pleased with how you handled the “Debt Records” program, Aling Nena has decided to hire you part time in order to computerize every aspect of her shop.

Part of this job is to create a system to to handle the inventory of her shop’s goods. So for your next task, you need to create a program which maintains the list of products in her shop.

This is a good time to discuss how to create a program from scratch i.e. without using therails generate scaffold script.

The Plan

The Product Maintenance program has 6 different parts:

  • User must be able to create a product record
  • User must be able to view the product record
  • User must be able to view a list of product records
  • User must be able to modify a product record
  • User must be able to delete a product record
  • User must be able to search for a product record

In addition to this, we also need to setup the database for a Products table with the appropriate fields. In the end, we have 7 tasks to be done in the following order:

  1. Create the migration for Product
  2. Program the “Show Product” module
  3. Program the “List Products” module
  4. Program the “Delete Product” module
  5. Program the “Search Products” module
  6. Program the “Create Product” module
  7. Program the “Edit Product” module

We’ve chosen this order of tasks so that the tasks are done in increasing coding complexity. So without further ado, let’s proceed with the first 2 tasks.

Viewing a Record

First up is the “Show Product” module, a page that displays the details of a certain Product record. We’ve combined this step with the “generate migration” step because we’re going to generate the migration along the way anyway.

The basic flow for showing a single record from the user is as follows:

  1. User provides the id of the record via the URL
  2. Program retrieves the record from database
  3. Program renders the retrieved data into a web page and sends it to the user

In this lesson, we shall see how these steps are coded in Rails.

Generating a Model and adding test data

Instead of usingrails generate scaffold, we shall use rails generate model, a script that only generates the migration and the model for the specified model and fields. The syntax of the latter is the same as the former:

$ rails generate model Product name:string description:text cost:decimal stock:integer 

We need to modify the generated migration to add some dummy data for our new screen. Add the highlighted lines to the migration file:

class CreateProducts < ActiveRecord::Migration
  def self.change
    create_table :products do |t|
      t.string :name
      t.text :description
      t.decimal :cost
      t.integer :stock

      t.timestamps
    end
    Product.create :name => "test product 1", :description => "test description 1", 
                   :cost => 1.11, :stock => 10
    Product.create :name => "test product 2",
                   :description => "<b>test description 2</b>", 
                   :cost => 2.22, :stock => 20
  end
end

Generating a Controller and View Page Templates

A controller action (not to be confused with Action Controller) is a public instance method inside the controller class that processes requests from the user. We can generate controller actions and their corresponding views with the use of therails generate controller script. The syntax for the script is:

rails generate controller controller_name action [action2 action3 ...] 

Rails Conventions

The name of the controller must be the plural form of the model.

The name of the action for showing the details of a single model is show.

By default, after processing a controller action, Rails will render the view file named [action_name].html.erb in the folder app/views/[controller_name]/. So for the show action of the products controller, it will render app/views/products/show.html.erb.

Given the conventions, here’s the script for generating our controller and view:

$ rails generate controller products show 

This script will create the controller app/controllers/products_controller.rb:

class ProductsController < ApplicationController
  def show
  end

end

and also the view app/views/products/show.html.erb:

<h1>Products#show</h1>
<p>Find me in app/views/products/show.html.erb</p>

You can now test the new controller and view by running the server (rails server) and going to http://localhost:3000/products/show. The following page should be displayed (you may have to restart the server through rails server):

The show action in the controller automatically refers to the show.html.erb file even without additional code as mentioned in the Rails conventions above.

We still have 2 problems at this point:

  1. We still haven’t displayed the details of the record.
  2. The format of the URL is different from the convention used by the code generated fromrails generate scaffold i.e. http://localhost:3000/products/1.

The 2nd problem is simpler and deals with routing so let’s tackle that first.

Routing

Routing deals with how different pages and controllers are linked to each other in a system. In Rails, there is a central configuration file that determines the available routes in the system. At first glance this may look like a violation of the Convention over Configuration mantra, but as we shall see much later, there are ways to let this routing file use convention to reduce the amount of configuration needed.

For this tutorial, we shall do away with the convention-related shortcuts so that you could understand how rails routes pages together. Take a look at the generated config/routes.rb file:

AlingnenaApp::Application.routes.draw do
  get "products/show"
  resources :debts  

  # The priority is based upon order of creation: first created -> highest priority.
  # See how all your routes lay out with "rake routes".

  # You can have the root of your site routed with "root"
  # root 'welcome#index'

  # Example of regular route:
  #   get 'products/:id' => 'catalog#view'

  # Example of named route that can be invoked with purchase_url(id: product.id)
  #   get 'products/:id/purchase' => 'catalog#purchase', as: :purchase

  # Example resource route (maps HTTP verbs to controller actions automatically):
  #   resources :products

  # Example resource route with options:
  #   resources :products do
  #     member do
  #       get 'short'
  #       post 'toggle'
  #     end
  #
  #     collection do
  #       get 'sold'
  #     end
  #   end

  # Example resource route with sub-resources:
  #   resources :products do
  #     resources :comments, :sales
  #     resource :seller
  #   end

  # Example resource route with more complex sub-resources:
  #   resources :products do
  #     resources :comments
  #     resources :sales do
  #       get 'recent', on: :collection
  #     end
  #   end

  # Example resource route with concerns:
  #   concern :toggleable do
  #     post 'toggle'
  #   end
  #   resources :posts, concerns: :toggleable
  #   resources :photos, concerns: :toggleable

  # Example resource route within a namespace:
  #   namespace :admin do
  #     # Directs /admin/products/* to Admin::ProductsController
  #     # (app/controllers/admin/products_controller.rb)
  #     resources :products
  #   end
end

Ruby Corner – Comments

Single line comments in Ruby begin with a hash sign (#).

Multi-line comments in Ruby are rare, but FYI, they are enclosed between =begin and =end.

It can be a daunting file at first glance, but removing all of the comments, we can see that it’s a pretty simple file:

AlingnenaApp::Application.routes.draw do
  get "products/show"

  resources :debts  
end

Before we proceed with creating our routes, let’s discuss first the idea behind the routing conventions in Rails: REST.

Representational State Transfer (REST)

Representational State Transfer (REST) is a scheme for communicating between clients and servers using HTTP. It was promoted as a stateless means of communication in a 2000 paper by Roy Fielding.

We will not go into detail about REST, and instead, we shall only focus on the parts that are relevant to us in Rails. In that regard, there are two main points about REST that we should know:

  1. All resources are uniquely identified by a URL. For example, a product record can be identified with the following URL:

    http://my.url/products/1

    In contrast, an item done in a non-REST framework might look like this

    http://my.url/view_product.do

    In this case, all product records share the same URL and are differentiated only based on the user’s state, either via session or cookie.

  2. Actions done on resources are defined by the HTTP verb of the request. Here are some examples:
    • GET http://my.url/productsget (a list of) all products
    • POST http://my.url/productspost a new product to the list of products
    • GET http://my.url/products/1get the product with the id of 1
    • PUT http://my.url/products/1put an updated product with the id of 1 to the database
    • DELETEhttp://my.url/products/1delete a product with the id of 1

The 5 HTTP commands in the 2nd point above are, by convention, how resources should be handled in Rails.

Now that we know the convention for the URL (http://localhost:3000/products/1) and the convention for the controller action (method must be namedshow for viewing records), we can now move on to connecting the two in our config/routes.rb.

Configuring Routing in routes.rb

Going back to the diagram in MVC, all browser requests to our Rails server goes to the routing component of the controller first. At this point, Rails checks entries inside the routes.rb file from top to bottom to see which controller and action to call.

We want to add a new “route” to the show action in theproducts controller and we can do this using the get declaration inside the routing block. The basic syntax of get is:

get(path_string, options_hash)

When Rails processes a GET request then reaches a get line in the routes.rb file, it first checks the path_string if it matches the request’s URL. The Ruby symbols in the path_string are placeholders and may match any part of the URL. When these symbols are matched to a part of the URL, they are converted into parameters inside aparams hash. (See Ruby Corner – Hash below for a discussion on hashes)

For example, if you added a catch-all get routing handler like:

get ':controller(/:action(/:id(.:format)))' 

and then you called http://localhost:3000/products/show/1, Rails would match this request with the route get ':controller(/:action(/:id(.:format)))' producing the following parameters:

params = { :controller => 'products', :action => 'show', :id => 1 } 

Based on this params hash, Rails can now route the request to the controller and action we created earlier.

We don’t need to define the :controller and :action symbols within a route. We can specify them as defaults using the => form of match:

get('products/show' => 'products#show') 

Opening http://localhost:3000/products/show will make Rails use the controller and action defined in the right side, in this case, the show action of the products controller.

At this point, we can create a route that matches Rails’s conventions on “show record” pages:

AlingnenaApp::Application.routes.draw do
  resources :debts
  get('products/:id' => 'products#show')  
 
end

Going to http://localhost:3000/products/1 will create the following parameters:

params = { :controller => 'products', :action => 'show', :id => 1 } 

As you can see, while the whole URL is matched by the path (‘products/:id’) only the :id part is converted to a parameter. The rest of the parameters for controller and action are provided as defaults at the right of the => symbol.

Testing http://localhost:3000/products/1, we now see the following page:

We’ve completed the routing setup. We can now proceed to building the rest of the “Show Record” program.

Ruby Corner – Hash

Ruby has a built-in collection for storing key-value pairs, the hash. Ruby is a dynamically typed language so you can use any data type for the keys and values.

Hashes can be initialized by a pair of curly braces. Initial key-value pairs can be added using the hashrocket symbol (“⇒”). For example:

my_hash = {}
another_hash = { :key =>"value", 1 => "a number" }

One way of visualizing hashes is to think of them as lookup tables. Here’s one for another_hash:

key value
:key “value”
1 “a number”

Values are retrieved and set with the use of square brackets ([]):

puts another_hash[:key]
my_hash["test"] = :value

Please refer to the API docs for other Hash methods.

Ruby Shortcuts

Since version 1.9, Ruby Hashes now support a JSON-like format aside from the original hashrocket syntax. For example:

yet_another_hash = { :foo => "bar", :model => "resource" }

Is the same as:

yet_another_hash = { foo: "bar", model: "resource" }

This only works for hashes that use simple symbols as keys (ie. we can’t use it in our get('products/:id... example above), but you will have many opportunities to use this syntax because most of the time you are going to use simple symbols as keys.

Ruby Shortcuts

The curly braces are optional when the hash is the last argument in a method call. Ruby treats the following method calls

get(':controller/:action/:id', { :defaults => { :id => 1 } } )

get(':controller/:action/:id', :defaults => { :id => 1 } ) 

as both having only 2 arguments. We can remove the parenthesis to further reduce the characters in the method call:

get ':controller/:action/:id', :defaults => { :id => 1 }

From this point on, we shall use this form for get.

Putting it all together: Retrieving a Record

To recall, our basic flow for this program is:

  1. User provides the id of the record via the URL – already done
  2. Program retrieves the record from database
  3. Program renders the retrieved data into a web page and sends it to the user

Steps 2 and 3 can be better illustrated by the MVC diagram we had before for Rails:

Retrieve Record in the Controller using Model

According to the diagram, retrieving the data has 3 steps:

  1. After the routing dispatches the request to the controller, the controller calls the model to retrieve the data.
  2. The model retrieves the data from the database.
  3. The model sends the data back to the controller.

The first half of the first step has already been done. All requests to 'products/:id' are now dispatched to the Products controller’s show method. All that is left to do is to actually utilize the model to retrieve data from the database. We can do the remaining steps by inserting the following line to theshow method:

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
  end
end

Retrieving the data is done using the Product.find(id) method, a class method in Active Record that retrieves a record with an id matching the passed id argument. In the statement above, this id argument is taken from the params hash passed to the controller via routing.

To pass the data to the controller, we store the result of the find method to an instance variable of the controller. This instance variable will be passed by the controller to the view in the next part of the process.

Ruby Corner – Class Methods

Class methods (aka static methods to Java people) are methods that can be called even without an instance of the class. Product.find above is a class method.

Class methods can be declared by adding “self.” before the method name. For example, the change method in our migrations are declared as class methods that are eventually called by Rails.

class SomeMigration < ActiveRecord::Migration
  def self.change
  end
end

Ruby Corner – Instance Variables

Instance variables are variables with an @ symbol before the variable name. As expected from instance variables, their scope extends to the entire class.

Instance variables are not publicly accessible. They can only be accessed through accessor and mutator (getter and setter) methods. We can make it look like the instance variables are publicly accessible by the use of operator overloading:

class Square
  def side_length
    @side_length
  end

  def side_length=(new_length)
    @side_length = new_length
  end
end

s = Square.new
puts s.side_length         # returns nil
s.side_length = 10
puts s.side_length         # returns 10   

Rails Conventions

You might have noticed that we didn’t define the id field anywhere in our scripts or in our migration:

...
    create_table :products do |t|
      t.string :name
      t.text :description
      t.decimal :cost
      t.integer :stock

      t.timestamps
    end
...

This is because by default, Rails adds “id”, an auto-incrementing integer primary key field, to all tables. By defining this field, developers don’t need to worry about defining primary keys in their tables. Also, as we shall see later, this scheme makes table relationships easier to code.

Another thing to note is that scripts that generate table migrations automatically add two timestamp fields, created_at and updated_at, through the t.timestamps declaration. As their names imply, created_at contains the timestamp when the record was created, while updated_at contains the timestamp when the record was last updated.

Display Model data using View

We only have 2 more steps in the Rails MVC diagram:

  1. The controller sends the data to the view.
  2. The view renders the web page based on the data and sends it to the user.

Rails automatically handles the first step: when you pass control over to the view at the end of the action method, Rails gives the view a bunch of variables to work on:

  • special variables like the params hash are available in the view through accessor methods
  • all instance variables in the controller are created instance variable counterparts in the view i.e. when you put values in the @product instance variable in the controller, you can access it in the view as@product

Now that we know that we can access @product in the view, the contents of our view should be easy to code. Replace the contents of app/views/products/show.html.erb to:

<h1>Show Product</h1>

<p>
  <strong>Name:</strong>
  <%= @product.name %>
</p>

<p>
  <strong>Description:</strong>
  <%= @product.description %>
</p>

<p>
  <strong>Cost:</strong>
  <%= number_to_currency(@product.cost, {:unit => 'PhP'}) %>
</p>

<p>
  <strong>Stock:</strong>
  <%= @product.stock %>
</p>

Opening http://localhost:3000/products/1 will give us the following result:

And with that, our “Show Product” page is now complete.

Ruby Corner – ERb expressions <%= %>

Our view uses Embedded Ruby (ERb) files to define the contents of the view. As the name suggests, these files are basic text files with some Ruby embedded in them to provide dynamic content.

In our example above, we used <%= %> to insert values into our page. When processing ERb files, Rails scans the files for <%= %> and replaces them with the value of the Ruby expressions inside them. For instance, you could add the following line to the view to print out the current date and time:

The time now is <%= Time.now %>

J2EE developers might be familiar with this construct: it’s the same thing used to insert dynamic values in JSP pages. It’s called an “expression” in the JSP world, so we’ll use that term here too.

Ruby Corner – Helper Methods

Rails provides some helper methods to use inside a view. For example, we used the number_to_currency method in our view above.

The number_to_currency method is self-explanatory; it simply converts the number provided to currency. It also provides some options for changing the currency symbol, the separator symbol, the format of the currency, etc.

Ruby Corner – Escaping HTML characters

Rails automatically escapes HTML characters in views. Try visiting http://localhost:3000/products/2 and check the contents of the description field.

Rails provides a way of outputting raw data via theraw helper method. Try reloading the same page after enclosing the data with a raw method call:

<%= raw(@product.description) %>

Escaping HTML important in web pages: if we do not escape text, malicious users can insert HTML code into our pages and attack our users with Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) attacks. A simple example would be to go back into our Debts program and create a debt with “<script>alert('hello world!')</script>” as a customer or item, then add raw calls in either the index or the show pages.

Every user who views the item now has a JavaScript code run in their browsers. At this point, hacking the user’s personal information should only take the hacker a few more lines of code. Therefore, one must always take care in using displaying raw output via the raw helper.

Shortcut Corner

Following our method shortening tips, we can shorten our expressions to:

<p>
  <b>Cost:</b>
 <%= number_to_currency @product.cost, unit: 'PhP' %></p>
</p>

Adding a List Page

Let’s move on to our List Products page. The approach is similar to the Show Product page, namely, we retrieve the records then render them in the view.

Preparing the New Files

We don’t need to create or modify anything for the view so let’s proceed with the controller.

Rails Conventions

For screens that list all products, the conventions are as follows:

  • the controller action should be namedindex
  • the URL should be /[controller] e.g. /products. Note the convention for controllers to be in plural form.

There’s no script for updating the controller so we have to do the changes by hand. First, let’s apply the first convention above to the controller and view. Insert a new action index to the controller (the order of the actions do not matter):

class ProductsController < ApplicationController
  def index
  end

  def show
    @product = Product.find(params[:id])
  end
end

Let’s also create a dummy view for the action, app/views/products/index.html.erb:

<h1>Listing Products</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Description</th>
      <th>Cost</th>
      <th>Stock</th>
    </tr>
  </thead>
  
</table>

For the 2nd convention, we add a new route in config/routes.rb for routing the request to the controller action:

AlingnenaApp::Application.routes.draw do
  resources :debts

  get 'products' => 'products#index' 
  get 'products/:id' => 'products#show'
 
end

You can now verify the routing by going to http://localhost:3000/products.

Retrieving All Records

In “show product”, we used the Active Record method find() to find a single record. For “list products”, we shall use all(). Add the following line to the index action:

  def index
    @products = Product.all
  end

Using the all() class method instructs Active Record to retrieve all of records of the model. This variation of the method returns an array of objects instead of a single object.

Ruby Corner – Arrays

Arrays in Ruby are similar to arrays in C-based languages. They’re zero-indexed (i.e. the first element is indexed at 0) and access and modification is done using square braces ([]):

puts array[3]   # outputs the contents of the 4th element of the array
array[1] = 10   # sets the 2nd element of the array to 10

Arrays can also be initialized using square braces:

my_array = ["one","two","three","four","five","six"] 

You can append new items into an array by using the << operator.

another_array = [ 10 ]
another_array << 20    # the array now contains [ 10, 20 ] 

Like in hashes, you can put any combination of data types inside arrays:

# array contains a number, string, symbol and an array
mixed_array = [ 10, "twenty", :thirty, [ 40, 50 ] ]   

Ruby includes some built in functions for arrays. Here are some examples:

puts an_array.size     # outputs the size of the array
an_array.delete_at(2)  # deletes the 3rd element of the array

Please refer to the API docs for other Array methods.

Displaying the Records

All we need to do now is to display the @products array in the view. This part is slightly complicated so it’s OK to take a peek at how our Debts program handles it.

<tbody>
  <% @debts.each do |debt| %>
    <tr>
      <td><%= debt.name %></td>
      <td><%= debt.item %></td>
      <td><%= debt.amount %></td>
      <td><%= debt.remarks %></td>
  ...
    </tr>
  <% end %>
</tbody>

There are three new concepts in this code snippet alone. Let’s discuss two of them before we proceed with adding the actual code our view.

Ruby Corner – Iteration

Ruby has for and while looping constructs but Ruby programmers rarely use them.

Why?

Because Ruby provides many ways of iterating through numbers and lists thanks to its functional programming influences. For example, a simple repetition task can be written as:

# print "hello world!" 10 times
10.times do 
  puts "hello world!"
end

Iterating through a list of numbers can be written using:

# print numbers 1 to 10 
1.upto(10) do |number| 
  puts number
end
# print numbers 10 down to 1
10.downto(1) do |number| 
  puts number
end

When you call upto() or downto(), the method passes the current number to the variable enclosed in the pipe symbols (|) every iteration. The iteration method for arrays is similar:

# print the contents of the array
an_array.each do |item| 
  puts item
end

Please refer to the API docs for other iteration related methods. You can tell if a method is an iteration related method if the final parameter for it is a block. (As we shall see later, blocks can either be enclosed in curly braces or a do-end block.) For arrays, you can look at both the API forArray as well as the API for its included class Enumerable.

# print the contents of the array using Enumberable#each_with_index
an_array.each_with_index do |item, index| 
  puts "The element at index #{index} is #{item}"
end
# adds 10 to each element in the array using Array#collect!
an_array.collect! do |item| 
  item + 10
end

Ruby Corner – Scriptlets <% %>

We’ve already discussed how expressions (<%= %>) insert values into our ERb views. To insert actual code inside the ERb we can use the <% %> construct (note the lack of =). Here’s a simple example, rendering the contents of a variable initialized within an ERb:

<% temp_variable = 20 * 1024 %>
<%= temp_variable %>

Code blocks are possible between these embedded ruby scripts. These blocks are rendered for every iteration. For example, this code renders the contents of an array in an unordered HTML list:

<ul>
<% an_array.each do |item| %>
  <li><%= item %></li>
<% end %>
</ul>

In J2EE / JSP terms, this construct is called a “scriptlet”. Like the JSP expressions, this ERb construct shares the same symbols as the JSP scriptlet. Because of this, we also call them “scriptlets” in this manual.

Now that we are familiar with iteration and scriptlets, we can now code the rest of the app/views/products/index.html.erb file:

<h1>Listing Products</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Description</th>
      <th>Cost</th>
      <th>Stock</th>
    </tr>
  </thead>
  <tbody>
    <% @products.each do |product| %>
      <tr>
        <td><%= product.name %></td>
        <td><%= product.description %></td>
        <td><%= number_to_currency(product.cost, :unit => "PhP")%></td>
        <td><%= product.stock %></td>
      </tr>
    <% end %> 
  </tbody>
</table>

Once coded, we can now go to http://localhost:3000/products to view the “list products” page:

Since we’re done with the “list products” page, we now have time to discuss the third concept introduced in the above code: Ruby Blocks and Procs.

Ruby Corner – Blocks and Procs

Blocks and Procs are the cornerstones of functional programming in Ruby. The way they are used in this programming language is so complex and elegant that explaining them in detail in this manual would be a wasted effort. Instead, we would refer you to this wonderful article for your reading pleasure.

Here’s a very short explanation on what happens in @products.each:

Blocks are like methods, but they still don’t have any bound data yet. They are declared using either

do |var1, var2, ...| 
  ...
end

or

{ |var1, var2, ...| ... }

When a block is passed to a method, the method would bind the values to the block and execute its statements. In the case of the each method, that method binds a new value into the block (e.g. the product variable) every iteration then runs the block. This is how the looping behavior works.

Linking the List Page with the Show Page

Before we proceed with deleting records, let’s add links between the index and show pages together first. It can be annoying to manually type http://localhost:3000/products and http://localhost:3000/products/1 just to go between these two pages.

For this, we can use the link_to helper method. This method returns a link based on the arguments you pass it. The syntax is as follows:

link_to(name, options = {}, html_options_hash = nil) 

The name argument is the text used in the link. The options argument can be three different things:

  • a string – this will be the URL used by the link. As this is brittle for internal use (paths can be easily changed in the routes.rb), this is only used for external links.
  • a hash – the link will be based on both the hash and the routes.rb.
  • a non-hash, non-string object – Rails will create a URL based on this object, usually an Active Record. This will be discussed after we finish the entire Product program.

Finally, the html_options_hash is simply a hash of HTML attributes you wish to assign to the link. For example, this link has the CSS class and the id set to “external-link” and “google-search”, respectively:

<%= link_to("Go to Google", "http://www.google.com", 
            { :class => "hello world!", :id => "google-search"}) %>

The resulting link is:

<a href="http://www.google.com" class="hello world!" id="google-search">Go to Google</a>

If the text is too long, you can use the block form of link_to:

link_to(options = {}, html_options_hash = nil) do
  # name
end

To use it, just put the text inside the block:

<%= link_to("http://www.google.com") do %>
  <strong>This link goes to Google</strong>
<% end %>  

Now that we know how to use link_to, let’s link the show and index pages. Insert the following line to index.html.erb:

...
<% @products.each do |product| %>
  <tr>
    <td><%= product.name %></td>
    <td><%= product.description %></td>
    <td><%= number_to_currency(product.cost, :unit => "PhP")%></td>
    <td><%= product.stock %></td>
    <td><%= link_to('Show', { :action => 'show', :id => product.id }) %></td>
  </tr>
<% end %>  
...

And add the following line to the end of show.html.erb:

<%= link_to('Back to List of Products', { :action => 'index' }) %>

Shortcut Corner

link_to is just another method so we could use the usual shortcuts for Ruby methods like removing the parenthesis and the curly braces:

<td><%= link_to 'Show', :action => 'show', :id => product.id %></td>

The tricky part here is that the method accepts 2 different hashes so you might think the shortcut won’t work. However, remember that Ruby parses the last series of hash key-value pairs as a single hash: if we want to pass 2 hashes, we just need to enclose the first hash in curly braces. For example, if we want to set the class of the link to the show product screen, we can use:

<td>
  <%= link_to 'Show', { :action => 'show', :id => product.id }, :class => 'link' %>
</td>

Named Routes

There are a couple of problems with using an options hash in link_to. The most obvious problem is that it’s too long. Even if the :controller and :action options can be derived from the current page, it’s still too long for practical usage.

Another problem is that it’s brittle. Suppose we have a link scattered around our system, say an approve account link. Initially our link goes to the approve_account action in the users controller so our links were generated using:

<%= link_to 'Approve Account', :controller => 'users', :action => 'approve_account' %>

Later, we changed the page that handles the account approval to theapprove action of the accounts controller. We would have to manually search all instances of the approve account link and modify it accordingly.

<%= link_to 'Approve Account', :controller => 'account', :action => 'approve' %>

To remedy these two problems, Rails provides a way for us to name routes in theroutes.rb file.

Here’s how you modify our routes for index and show to become named routes:

AlingnenaApp::Application.routes.draw do
  resources :debts
  get 'products' => 'products#index'
  get 'products/:id' => 'products#show'
  get 'products' => 'products#index', :as => 'products'
  get 'products/:id' => 'products#show', :as => 'product'
 
end

By adding an :as option, we tell Rails to provide two new helper functions based on the route:

  • xxxx_path – returns the relative path of the matched route. For example, calling products_path will return /products
  • xxxx_url – returns the complete URL of the matched route. For example, calling products_url will return http://localhost:3000/products

The path and URL are interchangeable in almost all cases. For this manual, we shall use the xxxx_path methods due to it being used in generator scripts. Here’s the named route version of the link in show.html.erb:

<%= link_to 'Back to List of Products', products_path %>

When the path in the route contains placeholder symbols, both methods accept arguments to fill up these placeholders. For example, if we want to generate the URL to a certain product, we can use:

...
    <td><%= product.stock %></td>
    <td><%= link_to 'Show', action: 'show', id: product.id %></td>
    <td><%= link_to 'Show', product_path(product.id) %></td>
  </tr>
<% end %>  
...

Shortcut Corner

You can directly provide Active Record objects when providing arguments for the named route helpers. The helpers will just retrieve the id of the record for you. For example:

...
    <td><%= link_to 'Show', product_path(product.id) %></td>
    <td><%= link_to 'Show', product_path(product) %></td>
...

Later, we shall see how we can directly use Active Record objects as arguments for link_to. This approach uses conventions in named routes. At this point, we can actually use our Product object because our named route matches the convention for routing.

...
    <td><%= link_to 'Show', product_path(product) %></td>
    <td><%= link_to 'Show', product %></td>
...