© Stefan Wintermeyer 2018
Stefan WintermeyerLearn Rails 5.2https://doi.org/10.1007/978-1-4842-3489-1_4

4. Scaffolding and REST

Stefan Wintermeyer1 
(1)
Bochum, Germany
 

Scaffolding means simply that a basic scaffold for an application is created with a generator. This scaffold not only contains the model but also a simple web GUI (views) and of course a controller. The programming paradigm used for this is Representational State Transfer (REST) .

You can find a definition of REST at wikipedia.org/wiki/Representational_state_transfer . My short and a bit oversimplified version is this: the inventor Roy Fielding described in 2000 how you can access data with a simple set of rules within the concept of CRUD and the specification of the Hypertext Transfer Protocol (HTTP). CRUD is the abbreviation for Create (SQL: INSERT), Read (SQL: SELECT), Update (SQL: UPDATE), and Delete (SQL: DELETE) . This created URLs that are easy to read for humans and have a certain logic. In this chapter, you will see examples showing the individual paths for the different CRUD functions.

I think the greatest frustration with Rails arises regularly from the fact that many beginners use scaffolding to get quick results without having the proper basic knowledge of Ruby and without knowing what ActiveRecord is. They don’t know what to do next. Fortunately, you have worked your way through Chapters 13, so you will be able to understand and use scaffolding straightaway.

Redirects and Flash Messages

Scaffolding uses redirects and flash messages . So, you have to make a little detour first to understand scaffolding.

Redirects

The name says it all, really. Redirects are commands that you can use within the controller to skip (i.e., redirect) to other web pages.

../images/460214_1_En_4_Chapter/460214_1_En_4_Figa_HTML.gif A redirect returns to the browser the response 302 Moved with the new target. So, each redirect does a round-trip to the browser and back.

Let’s create a new Rails project for a suitable example.

$ rails new redirect_example
[...]
$ cd redirect_example
$ rails db:migrate

Before you can redirect, you need a controller with at least two different methods. Here is a ping-pong example:

$ rails generate controller Game ping pong
Running via Spring preloader in process 51759
      create  app/controllers/game_controller.rb
       route  get 'game/pong'
       route  get 'game/ping'
      invoke  erb
      create    app/views/game
      create    app/views/game/ping.html.erb
      create    app/views/game/pong.html.erb
      invoke  test_unit
      create    test/controllers/game_controller_test.rb
      invoke  helper
      create    app/helpers/game_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/game.coffee
      invoke    scss
      create      app/assets/stylesheets/game.scss

The controller app/controllers/game_controller.rb has the content shown in Listing 4-1.

class GameController < ApplicationController
  def ping
  end
  def pong
  end
end
Listing 4-1

app/controllers/game_controller.rb

Now for the redirect: how can you set it up so you get immediately redirected to the method pong when you go to http://localhost:3000/game/ping? Easy, you say—you just change the route in config/routes.rb. And you are right. So, you don’t necessarily need a redirect. But if you want to process something else in the method ping before redirecting, then this is only possible by using a redirect_to in the controller app/controllers/game_controller.rb, as shown in Listing 4-2.

class GameController < ApplicationController
  def ping
   logger.info '+++  Example  +++'
   redirect_to game_pong_path
  end
  def pong
  end
end
Listing 4-2

app/controllers/game_controller.rb

But what is game_pong_path? Let’s take a look at the routes generated for this Rails application:

$ rails routes
   Prefix Verb URI Pattern          Controller#Action
game_ping GET  /game/ping(.:format) game#ping
game_pong GET  /game/pong(.:format) game#pong

../images/460214_1_En_4_Chapter/460214_1_En_4_Figb_HTML.gif As you can see, the route to the action ping of the controller GameController now gets the name game_ping (see the beginning of the line). You could also write the redirect like this:

redirect_to :action => 'pong'

I will explain the details and the individual options of the redirect later in the context of each specific case. For now, you just need to know that you can redirect not just to another method but also to another controller or an entirely different web page.

When you try to go to http://localhost:3000/game/ping, you are automatically redirected to http://localhost:3000/game/pong, and in the log output you see this:

Started GET "/game/ping" for 127.0.0.1 at 2015-04-15 17:50:04 +0200
Processing by GameController#ping as HTML
+++  Example  +++
Redirected to http://localhost:3000/game/pong
Completed 302 Found in 14ms (ActiveRecord: 0.0ms)
Started GET "/game/pong" for 127.0.0.1 at 2015-04-15 17:50:04 +0200
Processing by GameController#pong as HTML
  Rendered game/pong.html.erb within layouts/application (2.1ms)
Completed 200 OK in 2128ms (Views: 2127.4ms | ActiveRecord: 0.0ms)

redirect_to :back

If you want to redirect the user of your web application to the page the user was just on, you can use redirect_to : back . This is useful in a scenario where your user first has to log in to get access to a specific page.

Flash Messages

In my eyes, the term flash messages is somewhat misleading. Almost anyone would associate the term flash with more or less colorful web pages that were implemented with the Adobe Shockwave Flash plug-in. But in Ruby on Rails, flash messages are something completely different. They are messages that are displayed on the new page after a redirect, for example (see the section “Redirects”).

Flash messages are good friends with redirects. The two often work together in a team to give the user feedback on an action just carried out. A typical example of a flash message is the system feedback when a user has logged in. Often the user is redirected back to the original page and gets the message “You are now logged in.”

As an example, here again is the ping-pong scenario from the section “Redirects”:

$ rails new pingpong
      [...]
$ cd pingpong
$ rails db:migrate
$ rails generate controller Game ping pong
      [...]

You fill app/controllers/game_controller.rb with the content shown in Listing 4-3.

class GameController < ApplicationController
  def ping
   redirect_to game_pong_path, notice: 'Ping-Pong!'
  end
  def pong
  end
end
Listing 4-3

app/controllers/game_controller.rb

Now you start the Rails web server with rails server and use the browser to go to http://localhost:3000/game/ping. You are redirected from ping to pong. But the flash message “Ping-Pong!” is nowhere to be seen. You first need to expand app/views/layouts/application.html.erb, as shown in Listing 4-4.

<!DOCTYPE html>
<html>
  <head>
    <title>RedirectExample</title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <% flash.each do |name, message| %>
      <p><i><%= "#{name}: #{message}" %></i></p>
    <% end %>
    <%= yield %>
  </body>
</html>
Listing 4-4

app/views/layouts/application.html.erb

Now you see the flash message at the top of the page when you go to http://localhost:3000/game/ping in the browser, as shown in Figure 4-1.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig1_HTML.jpg
Figure 4-1

Flash message

If you go to http://localhost:3000/game/pong, you still see the normal Pong page . But if you go to http://localhost:3000/game/ping, you are redirected to the Pong page, and then the flash message is displayed at the top.

../images/460214_1_En_4_Chapter/460214_1_En_4_Figc_HTML.gif If you do not see a flash message that you were expecting, first check in the view to see whether the flash message is output there.

Different Types of Flash Messages

Flash messages are “automagically” passed to the view in a hash. By default, there are three different types : error, warning, and notice. You can also invent your own category and then get it in the view later.

You can set a flash message by writing the hash directly too, as shown here:

flash[:notice] = 'Ping-Pong!'

Please take a look at the official documentation at http://guides.rubyonrails.org/action_controller_overview.html#the-flash for more information.

Why Are There Flash Messages at All?

You may wonder why there are flash messages in the first place. Couldn’t you just build them yourself if you need them? Yes, indeed. But flash messages have the advantage that they offer a defined approach that is the same for any programmer. So, you don’t need to start from scratch every single time you need one.

Generating a Scaffold

Let’s first use scaffolding to create a list of products for an online shop . First, you need to create a new Rails application.

$ rails new scaffold-shop
  [...]
$ cd scaffold-shop
$ rails db:migrate

Let’s look at the scaffolding options.

$ rails generate scaffold
Usage:
  rails generate scaffold NAME [field[:type][:index] field[:type][:index]]
  [options]
[...]
Examples:
    `rails generate scaffold post`
    `rails generate scaffold post title body:text published:boolean`
    `rails generate scaffold purchase amount:decimal tracking_id:integer:uniq`
    `rails generate scaffold user email:uniq password:digest`

I’ll keep it short: for the current state of knowledge, you can use rails generate scaffold just like rails generate model. Let’s create the scaffold for the products.

$ rails generate scaffold product name 'price:decimal{7,2}'
Running via Spring preloader in process 38321
      invoke  active_record
      create    db/migrate/20180118065756_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/models/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/controllers/products_controller_test.rb
      create      test/system/products_test.rb
      invoke    helper
      create      app/helpers/products_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/products/index.json.jbuilder
      create      app/views/products/show.json.jbuilder
      create      app/views/products/_product.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/products.coffee
      invoke    scss
      create      app/assets/stylesheets/products.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss

As you can see, rails generate scaffold has already created the model. So, you can directly call rails db:migrate.

$ rails db:migrate
== 20180118065756 CreateProducts: migrating ===============================
-- create_table(:products)
   -> 0.0014s
== 20180118065756 CreateProducts: migrated (0.0015s) ==========================

Let’s create the first six products in db/seeds.rb.

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.

$ rails db:seed

The Routes

rails generate scaffold has created a route (more on this later in Chapter 5), a controller, and several views for you.

You could also have done all of this manually. Scaffolding is merely an automatism that does the work for you for some basic things. This is assuming that you always want to view, create, and delete records.

Without diving too deeply into the topic of routes, let’s just take a quick look at the available routes for the example. You need to run rails routes.

$ rails routes
      Prefix Verb   URI Pattern                  Controller#Action
    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
             PATCH  /products/:id(.:format)      products#update
             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 you had a look at the file app/controllers/products_controller.rb. The scaffolding automatically creates the methods index, show, new, create, update, and destroy. These methods or actions are called by the routes.

Listing 4-5 shows the content of app/controllers/products_controller.rb.

class ProductsController < ApplicationController
  before_action :set_product, only: [:show, :edit, :update, :destroy]
  # GET /products
  # GET /products.json
  def index
    @products = Product.all
  end
  # GET /products/1
  # GET /products/1.json
  def show
  end
  # GET /products/new
  def new
    @product = Product.new
  end
  # GET /products/1/edit
  def edit
  end
  # POST /products
  # POST /products.json
  def create
    @product = Product.new(product_params)
    respond_to do |format|
      if @product.save
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
        format.json { render :show, status: :created, location: @product }
      else
        format.html { render :new }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end
  # PATCH/PUT /products/1
  # PATCH/PUT /products/1.json
  def update
    respond_to do |format|
      if @product.update(product_params)
        format.html { redirect_to @product, notice: 'Product was successfully updated.' }
        format.json { render :show, status: :ok, location: @product }
      else
        format.html { render :edit }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end
  # DELETE /products/1
  # DELETE /products/1.json
  def destroy
    @product.destroy
    respond_to do |format|
      format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
      format.json { head :no_content }
    end
  end
  private
    # Use callbacks to share common setup or constraints between actions.
    def set_product
      @product = Product.find(params[:id])
    end
    # Never trust parameters from the scary internet, only allow the white list through.
    def product_params
      params.require(:product).permit(:name, :price)
    end
end
Listing 4-5

app/controllers/products_controller.rb

Let’s take a moment and go through this controller.

set_product

An action called before_action calls a private method to set an instance variable called @product for the actions :show, :edit, :update, and :destroy. That DRYs it up nicely.

before_action :set_product, only: [:show, :edit, :update, :destroy]
[...]
private
  # Use callbacks to share common setup or constraints between actions.
  def set_product
    @product = Product.find(params[:id])
  end
[...]

index

The index method sets the instance variable @products. It contains the result of Product.all.

# GET /products
# GET /products.json
def index
  @products = Product.all
end

show

The show method doesn’t do anything. set_product before_action already set the instance variable @ product . So, there is not more to do.

# GET /products/1
# GET /products/1.json
def show
end
new

The new method creates a new instance of Product and saves it in the instance variable @product.

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

edit

The edit method doesn’t do anything. The action called set_product before_action already set the instance variable @product. So, there is nothing more to do.

# GET /products/1/edit
def edit
end

create

The create method uses Product.new to create a new instance of Product and store it in @product. The private method product_params is used to filter the trusted parameters with a white list. When @product is successfully saved, a redirect to the show action is initiated for HTML requests. If a validation error occurs, the new action will be rendered.

# POST /products
# POST /products.json
def create
  @product = Product.new(product_params)
  respond_to do |format|
    if @product.save
      format.html { redirect_to @product, notice: 'Product was successfully created.' }
      format.json { render :show, status: :created, location: @product }
    else
      format.html { render :new }
      format.json { render json: @product.errors, status: :unprocessable_entity }
    end
  end
end
[...]
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
  params.require(:product).permit(:name, :price)
end

update

The update method tries to update @product with product_params. The private method product_params is used to filter the trusted parameters with a white list. When @product is successfully updated, a redirect to the show action is initiated for HTML requests. If a validation error occurs, the edit action will be rendered.

# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
  respond_to do |format|
    if @product.update(product_params)
      format.html { redirect_to @product, notice: 'Product was successfully updated.' }
      format.json { render :show, status: :ok, location: @product }
    else
      format.html { render :edit }
      format.json { render json: @product.errors, status: :unprocessable_entity }
    end
  end
end
[...]
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
  params.require(:product).permit(:name, :price)
end

destroy

The destroy method destroys @product and redirects an HTML request to the index action.

# DELETE /products/1
# DELETE /products/1.json
def destroy
  @product.destroy
  respond_to do |format|
    format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
    format.json { head :no_content }
  end
end

The Views

Now you start the Rails web server.

$ rails server
=> Booting Puma
=> Rails 5.2.0 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.0 (ruby 2.5.0-p0), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
A little drum roll, please, for dramatic suspense...launch the web browser and go to the URL http://localhost:3000/products. You can see the list of products as a simple web page, as shown in Figure 4-2.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig2_HTML.jpg
Figure 4-2

Products index

If you now click the link New Product , you will see an input form for a new record, as shown in Figure 4-3.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig3_HTML.jpg
Figure 4-3

New product form

Use your browser’s Back button to go back and click the Show link in the first line. You will then see the page shown in Figure 4-4.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig4_HTML.jpg
Figure 4-4

Showing a product

If you now click Edit , you will see the editing view for this record, as shown in Figure 4-5.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig5_HTML.jpg
Figure 4-5

Editing a product

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 ten minutes, you have written a web application that allows you to create, read/retrieve, update, and delete/destroy records. That is the scaffolding magic . You can save a lot of time.

Where Are the Views?

You can probably guess where the views are, but let’s take a look at the directory app/views/products anyway.

$ tree app/views/products/
app/views/products/
├── _form.html.erb
├── _product.json.jbuilder
├── edit.html.erb
├── index.html.erb
├── index.json.jbuilder
├── new.html.erb
├── show.html.erb
└── show.json.jbuilder

There are two different file extensions. The html.erb file is for HTML requests, and the json.jbuilder file is for JSON requests.

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 in accordance with the principle of DRY and is integrated into new.html.erb and edit.html.erb with a <%= render 'form' %>.

Let’s open the file app/views/products/index.html.erb, as shown in Listing 4-6.

<p id="notice"><%= notice %></p>
<h1>Products</h1>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Price</th>
      <th colspan="3"></th>
    </tr>
  </thead>
  <tbody>
    <% @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 %>
  </tbody>
</table>
<br>
<%= link_to 'New Product', new_product_path %>
Listing 4-6

app/views/products/index.html.erb

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.

link_to

In the views generated by the scaffold generator, you first came across the helper link_to. This creates <a hre ...> links. You can of course also enter a link manually via <a href="..."> in erb, but for links within a Rails project, link_to is more practical because you can use the names of the routes as a target. The code becomes much easier to read. In the previous example, there are the following routes:

$ rails routes
      Prefix Verb   URI Pattern                  Controller#Action
    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
             PATCH  /products/:id(.:format)      products#update
             PUT    /products/:id(.:format)      products#update
             DELETE /products/:id(.:format)      products#destroy

The first part of this route is the name of the route . With a new call, this is new_product. A link to new_product looks like the following in the erb code (you can see it at the end of the file app/views/products/index.html.erb):

<%= link_to 'New Product', new_product_path %>

In the HTML code of the generated page (http://localhost:3000/products), you can see the result.

<%= link_to 'New Product', new_product_path %>

With link_to you can also link to resources within a RESTful resource. Again, you can find examples for this in app/views/products/index.html.erb. In the table, a show link, an edit link, and a destroy link are rendered for each product.

<tbody>
  <% @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 %>
</tbody>

From the resource and the selected route, Rails automatically determines the required URL and the required HTTP verb (in other words, whether it is a POST, GET, PUT, or DELETE). For index and show calls, you need to observe the difference between singular and plural. link_to 'Show', product links to a single record, and link_to 'Show', products_path links to the index view.

Whether the name of the route is used with or without the suffix _path in link_to depends on whether Rails can “derive” the route from the other specified information. If only one object is specified (in this example, the variable product), then Rails automatically assumes that it is a show route.

Here are some examples:

ERD Code

Explanation

link_to 'Show', Product.first

Link to the first product

link_to 'New Product', new_product_path

Link to the web interface where a new product can be created

link_to 'Edit', edit_product_path(Product.first)

Link to the form where the first product can be edited

link_to 'Destroy', Product.first, method: :delete

Link to deleting the first product

form_for

In the partial used by new and edit, called app/views/products/_form.html.erb, you will find the code shown in Listing 4-7 for the product form.

<%= form_with(model: product, local: true) 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 |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :name %>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :price %>
    <%= f.text_field :price %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
Listing 4-7

app/views/products/_form.html.erb

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 complete <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 and be explained then (if they are not self-explanatory).

../images/460214_1_En_4_Chapter/460214_1_En_4_Figd_HTML.gif 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 |message| %>
      <li><%= message %></li>
    <% end %>
    </ul>
  </div>
<% end %>

Let’s add a small validation to the app/models/product.rb model , as shown in Listing 4-8.

class Product < ApplicationRecord
  validates :name,
            presence: true
end
Listing 4-8

app/models/product.rb

Whenever somebody wants to save a product that doesn’t have a name, Rails will show the flash error in Figure 4-6.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig6_HTML.jpg
Figure 4-6

Products error flash

Access via 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, you will see 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).

JSON (see http://wikipedia.org/wiki/Json ) seems to be the new cool kid. So, let’s use JSON.

If you do not require machine-readable access to data, you can remove the lines shown in Listing 4-9 from the file Gemfile (followed by the command bundle).

# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
Listing 4-9

Gemfile

Of course, you can delete the format.json lines manually too. But please don’t forget to delete the JSON view files too.

JSON As Default

Right at the beginning of app/controllers/products_controller.rb you will find the entry for the index action, as shown in Listing 4-10.

# GET /products
# GET /products.json
def index
  @products = Product.all
end
Listing 4-10

app/controllers/products_controller.rb

The code is straightforward. In the instance variable @products, all the products are saved. The view app/views/products/index.json.jbuilder contains the code shown in Listing 4-11 to render the JSON.

json.array! @products, partial: 'products/product', as: :product
Listing 4-11

app/views/products/index.json.jbuilder

It renders the partial named _product.json.jbuilder, as shown in Listing 4-12.

json.extract! product, :id, :name, :price, :created_at, :updated_at
json.url product_url(product, format: :json)
Listing 4-12

app/views/products/_product.json.jbuilder

You can use your browser to fetch the JSON output. Just open http://localhost:3000/products.json and view the result. I installed a JSON view extension in my Chrome browser to get a nicer format, as shown in Figure 4-7.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig7_HTML.jpg
Figure 4-7

Products index JSON

If you do not want the JSON output, you need to delete the json.jbuilder files.

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. Listing 4-13 shows an example with app/controllers/products_controller.rb in the index action.

# GET /products
# GET /products.json
# GET /products.xml
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
Listing 4-13

app/controllers/products_controller.rb

When Should You Use Scaffolding?

You should never use scaffolding just for the sake of it. There are Rails developers who never use scaffolding and always build everything manually. I find scaffolding quite useful for quickly getting into a new project. But it is always just the beginning.

Example for a Minimal Project

Let’s assume you need a web page quickly with which you can list products and represent them individually. But you do not require an editing or deleting function. In that case, a large part of the code created via scaffolding would be useless and have to be deleted. Let’s try it as follows:

$ rails new read-only-shop
  [...]
$ cd read-only-shop
$ rails generate scaffold product name 'price:decimal{7,2}'
  [...]
$ rails db:migrate
  [...]

Now create db/seeds.rb with some demo products , as shown in Listing 4-14.

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)
Listing 4-14

db/seeds.rb

Populate it with this data :

$ rails db:seed

Because you need only index and show, you should delete the views that not required.

$ rm app/views/products/_form.html.erb
$ rm app/views/products/new.html.erb
$ rm app/views/products/edit.html.erb

The json.jbuilder views are not needed either.

$ rm app/views/products/*.json.jbuilder

The file app/controllers/products_controller.rb can be simplified with an editor. It should look like Listing 4-15.

class ProductsController < ApplicationController
  before_action :set_product, only: [:show]
  # GET /products
  # GET /products.json
  def index
    @products = Product.all
  end
  # GET /products/1
  # GET /products/1.json
  def show
  end
  private
    # Use callbacks to share common setup or constraints between actions.
    def set_product
      @product = Product.find(params[:id])
    end
end
Listing 4-15

app/controllers/products_controller.rb

You only need the routes for index and show . Please open the file config/routes.rb and edit it as shown in Listing 4-16.

Rails.application.routes.draw do
  resources :products, only: [:index, :show]
end
Listing 4-16

config/routes.rb

A rails routes command shows you that really only index and show are routed now.

$ rails routes
  Prefix Verb URI Pattern             Controller#Action
products GET  /products(.:format)     products#index
 product GET  /products/:id(.:format) products#show
If you now start the server with rails server and go to the URL http://localhost:3000/products, you get an error message , as shown in Figure 4-8.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig8_HTML.jpg
Figure 4-8

Products error message

The same message will be displayed in the log.

$ rails server
=> Booting Puma
=> Rails 5.2.0 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.0 (ruby 2.5.0-p0), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
Started GET "/products" for 127.0.0.1 at 2017-03-23 17:47:43 +0100
   (0.2ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Processing by ProductsController#index as HTML
  Rendering products/index.html.erb within layouts/application
  Product Load (0.2ms)  SELECT "products".* FROM "products"
  Rendered products/index.html.erb within layouts/application (126.3ms)
Completed 500 Internal Server Error in 149ms (ActiveRecord: 0.7ms)
ActionView::Template::Error (undefined method `edit_product_path' for #<#<Class:0x007f98eb1e8148>:0x007f98ea6620d0>
Did you mean?  edit_polymorphic_path):
    17:         <td><%= product.name %></td>
    18:         <td><%= product.price %></td>
    19:         <td><%= link_to 'Show', product %></td>
    20:         <td><%= link_to 'Edit', edit_product_path(product) %></td>
    21:         <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
    22:       </tr>
    23:     <% end %>
app/views/products/index.html.erb:20:in `block in _app_views_products_index_html_erb___4554496912710881403_70147378203280'
app/views/products/index.html.erb:15:in `_app_views_products_index_html_erb___4554496912710881403_70147378203280'

The error message states that you call the undefined method edit_product_path in the view app/views/products/index.html.erb. Because you route only index and show now, there are no more edit, destroy, or new methods anymore. So, you need to adapt the file app/views/products/index.html.erb in the editor as shown in Listing 4-17.

<h1>Products</h1>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Price</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <% @products.each do |product| %>
      <tr>
        <td><%= product.name %></td>
        <td><%= product.price %></td>
        <td><%= link_to 'Show', product %></td>
      </tr>
    <% end %>
  </tbody>
</table>
Listing 4-17

app/views/products/index.html.erb

While you are at it, you can also edit app/views/products/show.html.erb accordingly; see Listing 4-18.

<p>
  <strong>Name:</strong>
  <%= @product.name %>
</p>
<p>
  <strong>Price:</strong>
  <%= @product.price %>
</p>
<%= link_to 'Back', products_path %>
Listing 4-18

app/views/products/show.html.erb

Now the application is finished. Start the Rails server with rails server and open the URL http://localhost:3000/products in the browser, as shown in Figure 4-9.
../images/460214_1_En_4_Chapter/460214_1_En_4_Fig9_HTML.jpg
Figure 4-9

Read-only products index

../images/460214_1_En_4_Chapter/460214_1_En_4_Fige_HTML.gif In this example, I am not commenting on the required changes in the tests, as this is not an exercise for test-driven development (TDD) but is meant to demonstrate a way of working with scaffolding. TDD developers will quickly be able to adapt the tests.

Conclusion

Try working with scaffolds one time and without them the next. Then you will soon get a feel for whether they fit into your workflow. I find that scaffolding makes my work much easier for standard applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.77.149