5.2. Generating a Scaffold

Let's first use scaffolding to create a list of products for an online shop. First, we need to create a new Rails application:
$ rails new shop
  [...]
$ cd shop
$ 
Let's look at the scaffolding options:
$ rails generate scaffold
Usage:
  rails generate scaffold NAME [field[:type][:index] field[:type][:index]] [options]

Options:
      [--skip-namespace]                        # Skip namespace (affects only isolated applications)
      [--old-style-hash]                        # Force using old style hash (:foo => 'bar') on Ruby >= 1.9
  -o, --orm=NAME                                # Orm to be invoked
                                                # Default: active_record
      [--force-plural]                          # Forces the use of a plural ModelName
      --resource-route                          # Indicates when to generate resource route
                                                # Default: true
  -y, [--stylesheets]                           # Generate Stylesheets
                                                # Default: true
  -se, [--stylesheet-engine=STYLESHEET_ENGINE]  # Engine for Stylesheets
                                                # Default: scss
  -c, --scaffold-controller=NAME                # Scaffold controller to be invoked
                                                # Default: scaffold_controller
      [--assets]                                # Indicates when to generate assets
                                                # Default: true

ActiveRecord options:
      [--migration]            # Indicates when to generate migration
                               # Default: true
      [--timestamps]           # Indicates when to generate timestamps
                               # Default: true
      [--parent=PARENT]        # The parent class for the generated model
      [--indexes]              # Add indexes for references and belongs_to columns
                               # Default: true
  -t, [--test-framework=NAME]  # Test framework to be invoked
                               # Default: test_unit

TestUnit options:
      [--fixture]                   # Indicates when to generate fixture
                                    # Default: true
  -r, [--fixture-replacement=NAME]  # Fixture replacement to be invoked

ScaffoldController options:
  -e, [--template-engine=NAME]  # Template engine to be invoked
                                # Default: erb
      [--helper]                # Indicates when to generate helper
                                # Default: true

Runtime options:
  -f, [--force]    # Overwrite files that already exist
  -p, [--pretend]  # Run but do not make any changes
  -q, [--quiet]    # Suppress status output
  -s, [--skip]     # Skip files that already exist

Description:
    Scaffolds an entire resource, from model and migration to controller and
    views, along with a full test suite. The resource is ready to use as a
    starting point for your RESTful, resource-oriented application.

    Pass the name of the model (in singular form), either CamelCased or
    under_scored, as the first argument, and an optional list of attribute
    pairs.

    Attributes are field arguments specifying the model's attributes. You can
    optionally pass the type and an index to each field. For instance:
    "title body:text tracking_id:integer:uniq" will generate a title field of
    string type, a body with text type and a tracking_id as an integer with an
    unique index. "index" could also be given instead of "uniq" if one desires
    a non unique index.

    Timestamps are added by default, so you don't have to specify them by hand
    as 'created_at:datetime updated_at:datetime'.

    You don't have to think up every attribute up front, but it helps to
    sketch out a few so you can start working with the resource immediately.

    For example, 'scaffold post title body:text published:boolean' gives
    you a model with those three attributes, a controller that handles
    the create/show/update/destroy, forms to create and edit your posts, and
    an index that lists them all, as well as a resources :posts declaration
    in config/routes.rb.

    If you want to remove all the generated files, run
    'rails destroy scaffold ModelName'.

Examples:
    `rails generate scaffold post`
    `rails generate scaffold post title body:text published:boolean`
    `rails generate scaffold purchase amount:decimal tracking_id:integer:uniq`
$ 
I'll keep it short: for our current state of knowledge, we can use rails generate scaffold just like rails generate model (see Section 4.2, “Creating Database/Model). Let's now create the scaffold for the products:
$ rails generate scaffold product name 'price:decimal{7,2}' 
      invoke  active_record
      create    db/migrate/20121121093040_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/unit/product_test.rb
      create      test/fixtures/products.yml
      invoke  resource_route
       route    resources :products
      invoke  scaffold_controller
      create    app/controllers/products_controller.rb
      invoke    erb
      create      app/views/products
      create      app/views/products/index.html.erb
      create      app/views/products/edit.html.erb
      create      app/views/products/show.html.erb
      create      app/views/products/new.html.erb
      create      app/views/products/_form.html.erb
      invoke    test_unit
      create      test/functional/products_controller_test.rb
      invoke    helper
      create      app/helpers/products_helper.rb
      invoke      test_unit
      create        test/unit/helpers/products_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/products.js.coffee
      invoke    scss
      create      app/assets/stylesheets/products.css.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.css.scss
$

Creating the Database with Sample Data

As you can see, rails generate scaffold has already created the model. So we can directly call rake db:migrate:
$ rake db:migrate
==  CreateProducts: migrating =================================================
-- create_table(:products)
   -> 0.0021s
==  CreateProducts: migrated (0.0023s) ========================================

$
Let's create the first six products in the db/seeds.rb. I am not quite sure about Walter Scheel, but after all, this book is all about Rails, not German post-war history.
Product.create(name: 'Apple', price: 1)
Product.create(name: 'Orange', price: 1)
Product.create(name: 'Pineapple', price: 2.4)
Product.create(name: 'Marble cake', price: 3)
Populate with the example data:
$ rake db:seed
$

The Routes

rails generate scaffold has created a route (more on this later in Chapter 6, Routes), a controller and several views for us (see Section 3.3, “Creating HTML Dynamically with erb”). We could also have done all of this manually. Scaffolding is merely an automatism that does the work for us for some basic things. This is assuming that you always want to view, create and delete records.
Without diving too deeply into the topic routes, let's just have a quick look at the available routes for our example. You need to enter rake routes:
$ rake routes
    products GET    /products(.:format)          products#index
             POST   /products(.:format)          products#create
 new_product GET    /products/new(.:format)      products#new
edit_product GET    /products/:id/edit(.:format) products#edit
     product GET    /products/:id(.:format)      products#show
             PUT    /products/:id(.:format)      products#update
             DELETE /products/:id(.:format)      products#destroy
$
These are all the routes and consequently URLs available in this Rails application. All routes invoke actions (in other words, methods) in the ProductsController.

The Controller

Now it's about time we had a look at the file app/controllers/products_controller.rb. Scaffold automatically creates the methods index, show, new, create, update and destroy. These methods or actions are called by the routes.
Before we go any further: you do not need to work your way through the controller line by line. What is important is that you know where the file is and that the individual methods for the calls by the routes are located there. Then you can go back later and look it up there if anything is unclear.
class ProductsController < ApplicationController
  # GET /products
  # GET /products.json
  def index
    @products = Product.all

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

  # GET /products/1
  # GET /products/1.json
  def show
    @product = Product.find(params[:id])

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

  # GET /products/new
  # GET /products/new.json
  def new
    @product = Product.new

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

  # GET /products/1/edit
  def edit
    @product = Product.find(params[:id])
  end

  # POST /products
  # POST /products.json
  def create
    @product = Product.new(params[:product])

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

  # PUT /products/1
  # PUT /products/1.json
  def update
    @product = Product.find(params[:id])

    respond_to do |format|
      if @product.update_attributes(params[:product])
        format.html { redirect_to @product, notice: 'Product was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /products/1
  # DELETE /products/1.json
  def destroy
    @product = Product.find(params[:id])
    @product.destroy

    respond_to do |format|
      format.html { redirect_to products_url }
      format.json { head :no_content }
    end
  end
end

The Views

Now we start the Rails web server:
$ rails server
=> Booting WEBrick
=> Rails 3.2.9 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2012-11-21 10:34:39] INFO  WEBrick 1.3.1
[2012-11-21 10:34:39] INFO  ruby 1.9.3 (2012-11-10) [x86_64-darwin12.2.1]
[2012-11-21 10:34:39] INFO  WEBrick::HTTPServer#start: pid=19507 port=3000

Note

If you get a warning from your built-in firewall now, this shows that you have not read Chapter 3, First Steps with Rails properly. ;-)
Now a little drum roll... dramatic suspense... launch the web browser and go to the URL http://0.0.0.0:3000/products. You can see the list of products as simple web page.
Index of Listed Products
If you now click the link "New Product", you will see an input form for a new record:
Input Form for New Record
Use your browser's Back button to go back and click on the "Show" link in the first line. You will then see the following page:
Viewing an Individual Record
If you now click "Edit", you will see the editing view for this record:
Editing Form for an Existing Record
And if you click "Destroy" on the Index page, you can delete a record after confirming the message that pops up. Isn't that cool?! Within less than 10 minutes, you have written a Web application that allows you to create, read/retrieve, update and delete/destroy records (CRUD). That is the scaffolding magic. You can save a lot of time.

Where Are the Views?

You can probably guess, but let's have a look at the directory app/views/products anyway:
$ ls app/views/products 
_form.html.erb
 edit.html.erb
 index.html.erb
 new.html.erb
 show.html.erb
$ 
For index, edit, new and show the corresponding views are located there. As new and edit both require a form for editing the data, this is stored in the partial _form.html.erb (see the section called “Partials”) in accordance with the principle of DRY (Don't Repeat Yourself) and integrated in new.html.erb and edit.html.erb with a <%= render 'form' %>.
Let's open the file app/views/products/index.html.erb:
<h1>Listing products</h1>

<table>
  <tr>
    <th>Name</th>
    <th>Price</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @products.each do |product| %>
  <tr>
    <td><%= product.name %></td>
    <td><%= product.price %></td>
    <td><%= link_to 'Show', product %></td>
    <td><%= link_to 'Edit', edit_product_path(product) %></td>
    <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New Product', new_product_path %>
You are now an old hand when it comes to ERB, so you'll be able to read and understand the code without any problems. If in doubt, have a quick look at the section called “Programming in an erb File”.

form_for

In the partial used by new and edit, app/views/products/_form.html.erb, you will find the following code for the product form:
<%= form_for(@product) do |f| %>
  <% if @product.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>

      <ul>
      <% @product.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :price %><br />
    <%= f.text_field :price %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
In a block, the helper form_for takes care of creating the HTML form via which the user can enter the data for the record or edit it. If you delete a <div class="field"> element here, this can no longer be used for input in the web interface. I am not going to comment on all possible form field variations at this point. The most frequently used ones will appear in examples later on and be explained then (if they are not self-explanatory).

Note

You can find an overview of all form helpers at http://guides.rubyonrails.org/form_helpers.html
When using validations in the model, any validation errors that occur are displayed in the following code at the head of the form:
  <% if @product.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>

      <ul>
      <% @product.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
Let's add a small validation to the app/models/product.rb model:
class Product < ActiveRecord::Base
  attr_accessible :name, :price

  validates :name,
            :presence => true
end
When ever somebody wants to save a product which doesn't have a name Rails will show this Flash Error:
Flash Error for a New Record

Access via XML or JSON

By default, Rails' scaffolding generates not just access via HTML for "human" users, but also a direct interface for machines. The same methods index, show, new, create, update and destroy can be called via this interface, but in a format that is easier to read for machines. As an example, we will demonstrate the index action via which all data can be read in one go. With the same idea, data can be removed (destroy) or edited (update).
Currently popular formats are XML (see http://en.wikipedia.org/wiki/Xml) and JSON (see http://en.wikipedia.org/wiki/Json).
If you do not require machine-readable access to data, you can skip these examples. But then you should also delete all lines with format.xml or format.json in the respond_to blocks of your controllers, to be on the safe side. Otherwise you still have an interface to the data that you may just forget and that constitutes a potential security gap.
JSON as Default
Right at the beginning of app/controllers/products_controller.rb you will find the entry for the index action:
class ProductsController < ApplicationController
  # GET /products
  # GET /products.json
  def index
    @products = Product.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @products }
    end
  end
The code is straightforward. In the instance variable @products, all products are saved. Then something happens with a format. Hmmm… it says something about json. Let's try this: start the Rails server again with rails server and use your web browser to go to the URL http://0.0.0.0:3000/products.json. Heureka! You will see this:
Index JSON View
The URL http://0.0.0.0:3000/products.json gives us all products in JSON format. In Rails 3.0, the default was still XML. But ultimately, it does not matter whether it's XML or JSON. The cool JavaScript programmers simply prefer JSON, and as an AJAX element can be found at every corner these days, it made sense to change the default from XML to JSON.
If you do not want the JSON output, you need to delete this line in the app/controllers/products_controller.rb.
JSON and XML Together
If you ever need a JSON and XML interface in a Rails application, you just need to specify both variants in the controller in the block respond_to. Here is an example with the app/controllers/products_controller.rb in the index action:
class ProductsController < ApplicationController
  # GET /products
  # GET /products.json
  def index
    @products = product.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @products }
      format.xml { render xml: @products }
    end
  end
[...]

Updates about this book will be published on my Twitter feed.