Learning Rails: Resource Generation

Table of Contents

(Coming Soon!) Validations and Testing »

Last time, I saw how easy it was to create a new application using the rails new command. I learned a bit about controllers, views and routes, including how to create my own. Today, I’m going to learn about resource generation and data models, and create a crude URL shortener.

Generating Resources

A Rails application is organized around resources. A resource is simply the object the user of the application will interact with. I can let Rails generate my resources for me using the rails generate scaffold command. Invoke the command by passing the name of the new resource and a list of the properties. In this case, I am creating the ShortUrl resource with a single string property named url.

$ rails generate scaffold ShortUrl url:string
      invoke  active_record
      create    db/migrate/20111029050110_create_short_urls.rb
      create    app/models/short_url.rb
      invoke    test_unit
      create      test/unit/short_url_test.rb
      create      test/fixtures/short_urls.yml
       route  resources :short_urls
      invoke  scaffold_controller
      create    app/controllers/short_urls_controller.rb
      invoke    erb
      create      app/views/short_urls
      create      app/views/short_urls/index.html.erb
      create      app/views/short_urls/edit.html.erb
      create      app/views/short_urls/show.html.erb
      create      app/views/short_urls/new.html.erb
      create      app/views/short_urls/_form.html.erb
      invoke    test_unit
      create      test/functional/short_urls_controller_test.rb
      invoke    helper
      create      app/helpers/short_urls_helper.rb
      invoke      test_unit
      create        test/unit/helpers/short_urls_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/short_urls.js.coffee
      invoke    scss
      create      app/assets/stylesheets/short_urls.css.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.css.scss

The scaffold generator created everything I needed to start working with my resource including the controller, routes, views, model and database migrations.

Database Migrations

Rails uses migrations to organize changes to the application’s underlying database. Migrations are ruby classes derived from ActiveRecord::Migration that describe discrete changes to a database schema. The application’s database can be upgraded or downgraded to any version using migrations.

The scaffold generator automatically created a migration for me using the fields specified on the command line.

class CreateShortUrls < ActiveRecord::Migration
  def change
    create_table :short_urls do |t|
      t.string :url
      t.timestamps
    end
  end
end

In this case, the migration will create a new short_url table with the url column. It also automatically includes an implicit primary key named id and two timestamp columns named created_at and updated_at. Rails automatically updates the timestamp columns when saving records to the database.

The resource generator created a database migration for me, but it did not automatically update my development database with the new schema. To update to the latest schema, I use the rake db:migrate command.

$ rake db:migrate
==  CreateShortUrls: migrating ================================================
-- create_table(:short_urls)
   -> 0.0013s
==  CreateShortUrls: migrated (0.0014s) =======================================

Rake, the ruby version of make, processes a Rakefile and provides various build actions. Rails’ default Rakefile already comes with most of the tasks required to manage the application. Rake will display all available tasks with the rake -T command.

My database has now been upgraded to the latest version and includes the new short_urls table. Rails also automatically creates a schema_migrations table which it uses to keep track of which migrations have already been performed on the database.

$ sqlite3 db/development.sqlite3
SQLite version 3.7.8 2011-09-19 14:49:19
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .schema
CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL);
CREATE TABLE "short_urls" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "url" varchar(255), "created_at" datetime, "updated_at" datetime);
CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");

Learn more about migrations at Rails Guides.

Model

The model represents the application’s data, in this case a short URL. It corresponds to the short_url table in the database. The generated model is very simple because Rails’ ActiveRecord::Base class already provides the functionality for linking the model to the database. For example, all fields in the database table are automatically exposed as properties of the model.

class ShortUrl < ActiveRecord::Base
end

I can see my models in action and interact with the database using the rails console command. The console is a great way to quickly test the models and business logic.

$ rails console
Loading development environment (Rails 3.1.1)
irb(main):001:0> s = ShortUrl.new(:url => "http://google.com/")
=> #
irb(main):002:0> s.save
  SQL (15.5ms)  INSERT INTO "short_urls" ("created_at", "updated_at", "url") VALUES (?, ?, ?)  [["created_at", Sat, 29 Oct 2011 18:44:09 UTC +00:00], ["updated_at", Sat, 29 Oct 2011 18:44:09 UTC +00:00], ["url", "http://google.com/"]]
=> true
irb(main):003:0> ShortUrl.find(:all)
  ShortUrl Load (0.2ms)  SELECT "short_urls".* FROM "short_urls"
=> [#]

RESTful Resources

Rails resources are modeled around REST, which stands for Representational State Transfer. When a client sends a request to an application, it is requesting the current state or a change in state of a resource. When using REST over the HTTP protocol, the URL identifies the resource and the HTTP method(GET, POST, PUT or DELETE) identifies the operation to be performed on the resource. This applies regardless of the format of the data. It could be in HTML, JSON or XML. This common vocabulary makes it easy to create and consume RESTful services.

Rails generates RESTful resources automatically when using rails generate scaffold. It creates a controller with the basic operations to view, create, update and destroy the resources, the views required to display the resource to the user, and the model used to represent the resource in the business logic. It also generates the routes required to map requests to actions on the controller.

For the ShortUrl resource, Rails automatically generated routes and a controller with the following operations.

Method Resource Action Description
GET short_urls index Gets a list of the ShortUrl resources.
GET short_urls/new new Gets an empty ShortUrl resource. In HTML, this is an empty form.
POST short_urls create Creates a new ShortUrl using the data passed in the POST data.
GET short_urls/:id show Gets the ShortUrl with the specified id.
GET short_urls/:id/edit edit Gets the ShortUrl with the specified id. In HTML, this is an edit form.
PUT short_urls/:id update Updates the ShortUrl with the specified id using the data passed in the PUT data.
DELETE short_urls/:id destroy Deletes the ShortUrl with the specified id.

The resource generator modified config/routes.rb so that requests on ShortUrls map to the new controller and actions as shown in the table. As is common using Rails’ convention over configuration, this is only a single line.

NjeniUs::Application.routes.draw do
  resources :short_urls
end

The rake routes command is used to view all the routes in the application.

$ rake routes
        short_urls GET    /short_urls(.:format)              {:controller=>"short_urls", :action=>"index"}
                   POST   /short_urls(.:format)              {:controller=>"short_urls", :action=>"create"}
     new_short_url GET    /short_urls/new(.:format)          {:controller=>"short_urls", :action=>"new"}
    edit_short_url GET    /short_urls/:id/edit(.:format)     {:controller=>"short_urls", :action=>"edit"}
         short_url GET    /short_urls/:id(.:format)          {:controller=>"short_urls", :action=>"show"}
                   PUT    /short_urls/:id(.:format)          {:controller=>"short_urls", :action=>"update"}
                   DELETE /short_urls/:id(.:format)          {:controller=>"short_urls", :action=>"destroy"}
        home_index GET    /home/index(.:format)              {:controller=>"home", :action=>"index"}
              root        /                                  {:controller=>"home", :action=>"index"}

Index Action

The index action, identified by the GET /short_urls request, returns the list of ShortUrls to the user.

  # GET /short_urls
  # GET /short_urls.json
  def index
    @short_urls = ShortUrl.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render :json => @short_urls }
    end
  end

The action first retrieves a collection of all ShortUrl models using ShortUrl.all and assigns that to the @short_urls variable. The responds_to block creates the output to the user based on the requested format. Rails automatically generated handlers for html and json requests. To view any resource in another format in the browser, append .format to the url. For example, http://localhost:3000/short_urls.json will display a JSON representation of the list of ShortUrls.

The .html format handler will, by default, display the view app/views/short_urls/index.html.erb. Any instance variables created in the action will also be available to the view.

<h1>Listing short_urls</h1>

<table>
  <tr>
    <th>Url</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @short_urls.each do |short_url| %>
  <tr>
    <td><%= short_url.url %></td>
    <td><%= link_to 'Show', short_url %></td>
    <td><%= link_to 'Edit', edit_short_url_path(short_url) %></td>
    <%= link_to 'Destroy', short_url, :confirm => 'Are you sure?', :method => :delete %>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New Short url', new_short_url_path %>

The view iterates over the items in the @short_url collection, the instance variable created in the action, and outputs an HTML table containing the data along with links to display, edit and delete each item. The link_to helper method is used to create hyperlinks to resources, while the resource helper methods, edit_short_url_path and new_short_url_path, generate URLs to the other actions on the resource. These helper methods are important because it separates the view from the routes so that if the route or action names ever change the view will still work.

Index View

The index view provides a place to list the URLs.

New and Create Actions

The new action, identified by the GET /short_urls/new request, displays a form allowing the user to create a new ShortUrl.

  # GET /short_urls/new
  # GET /short_urls/new.json
  def new
    @short_url = ShortUrl.new

    respond_to do |format|
      format.html # new.html.erb
      format.json { render :json => @short_url }
    end
  end

The action first creates a new blank ShortUrl model using ShortUrl.new and assigns that to the @short_url variable. The respond_to block works the same as it does in the index action; it displays the new view.

<h1>New short_url</h1>

<%= render 'form' %>

<%= link_to 'Back', short_urls_path %>

The <%= render 'form' %> line delegates the output of the form to the partial named form. A partial is a piece of code that can be reused in multiple places. In this case, the form used to create a new ShortUrl is also used to edit one.

<%= form_for(@short_url) do |f| %>
  <% if @short_url.errors.any? %>
    <div id="error_explanation"></pre></pre></pre>
<h2><%= pluralize(@short_url.errors.count, "error") %> prohibited this short_url from being saved:</h2>
<pre>
<pre>
<pre>
      <ul>
      <% @short_url.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :url %><br />
    <%= f.text_field :url %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

The form_for block creates an HTML form based on the specified resource. Inside the block, the f variable is used to generate form fields using the attributes of the specified resource. For example, f.text_field :url instructs Rails to create an input text field on the form and hook it to the url attribute of @short_url. The f.submit method generates a button that will submit the form when pressed. If there are any errors when the user submits the form, they will be redirected back to this form and the @short_url.errors attribute will contain a list of the errors. The view displays the errors at the top of the page to inform the user of the problem.

New URL View

The new view creates a URL.

The form’s action and method are hooked to the POST /short_urls request, which routes to the create action in the controller.

  # POST /short_urls
  # POST /short_urls.json
  def create
    @short_url = ShortUrl.new(params[:short_url])

    respond_to do |format|
      if @short_url.save
        format.html { redirect_to @short_url, :notice => 'Short url was successfully created.' }
        format.json { render :json => @short_url, :status => :created, :location => @short_url }
      else
        format.html { render :action => "new" }
        format.json { render :json => @short_url.errors, :status => :unprocessable_entity }
      end
    end
  end

The action starts by creating a new instance of a ShortUrl model initialized with ShortUrl.new(params[:short_url]). The params[:short_url] contains the POST data submitted by the form. The action then attempts to save the new ShortUrl with @short_url.save. If the save succeeds, the user is redirected to /short_urls/:id, which routes to the show action. If the save fails, Rails renders the new view again, displaying the form to the user along with the error messages indicating the reason for the failure.

Show Action

The show action, identified by the GET /short_urls/:id request, returns the details of the ShortUrl identified by :id to the user.

  # GET /short_urls/1
  # GET /short_urls/1.json
  def show
    @short_url = ShortUrl.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.json { render :json => @short_url }
    end
  end

The action first retrieves the ShortUrl model using ShortUrl.find(params[:id]) and assigns that to the @short_url variable. The responds_to block displays the show view to the user.

<p id="notice"><%= notice %></p>
<p>
  <strong>Url:</strong>
  <%= @short_url.url %>
</p>

<%= link_to 'Edit', edit_short_url_path(@short_url) %> |
<%= link_to 'Back', short_urls_path %>

The view simply displays the attributes of the ShortUrl along with an Edit link and a link back to the index action. <%= notice %> displays the notice parameter passed from the previous request, such as from the create or update actions.

Show View

The show view displays the details of the URL.

Edit and Update Actions

The edit and update actions work the same way as new and create, so I will not detail them here.

Destroy Action

The destroy action, identified by the DELETE /short_urls/:id request, deletes the specified ShortUrl from the system.

  # DELETE /short_urls/1
  # DELETE /short_urls/1.json
  def destroy
    @short_url = ShortUrl.find(params[:id])
    @short_url.destroy

    respond_to do |format|
      format.html { redirect_to short_urls_url }
      format.json { head :ok }
    end
  end

The action first retrieves the ShortUrl model using ShortUrl.find(params[:id]) and then deletes it using the destroy method. The responds_to block redirects the user back to the index action.

Adding a New Action

Now that I’ve learned how RESTful resources work and how the actions interact with the views, I am ready to create a new redirect action to get a bare-bones working URL shortener. It simply needs to retrieve a specified ShortUrl model and redirect the user to its url attribute.

  # GET /1
  # GET /short_urls/1/redirect
  # GET /short_urls/1/redirect.json
  def redirect
    @short_url = ShortUrl.find(params[:id])

    respond_to do |format|
      format.html { redirect_to @short_url.url, :status => :moved_permanently }
      format.json { render :json => @short_url } # What does it mean to redirect using json?
    end
  end

To stay consistent with the RESTful resources, the redirect action should be identified by the GET /short_urls/:id/redirect request. Additionally, to keep the URLs as short as possible, I also need to identify it as GET /:id. I can add both to the application in config/routes.rb.

NjeniUs::Application.routes.draw do
  resources :short_urls do
    member do
      get 'redirect'
    end
  end

  get "home/index"

  match ':id' => 'short_urls#redirect'

  root :to => "home#index"
end

Adding get 'redirect' to the member block of resources :short_urls instructs Rails to route the action named redirect on a ShortUrl. The match ':id' => 'short_urls#redirect' routes GET /:id to the same action.

Now when I navigate to /short_urls/:id/redirect or /:id I am automatically redirected to the correct URL.

Summary

Today I learned how to use rails generate scaffold to quickly generate an entire resource and how database migrations and models work. I researched RESTful services and how they translate to Rails using the routing engine, controllers and views. I even created my own redirect action to get a simple working URL shortener that I can use. It is very easy to get a working system up and running by leveraging Rails’ code generation and convention over configuration.

This version of the application can be accessed at http://v0.02.njeni.us/. Take a look if you want to see what I ended up with.

Next time I will investigate model validations and Rails’ testing framework to catch errors in the application.

  1. M Kashif says:

    Sir thats not fair. I was really enjoying your articles on ruby. awesome articles. but ended here. :( i wanted more. but still i appreciate your efforts. would hear more from you. Thanks for your efforts and time.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>