AWD 08: Tarea C: Despliegue del catalogo - LPC-Ltda/Ruby-on-Rails GitHub Wiki

##8.1 Iteración C1: Crear el listado del catálogo

Creamos un nuevo controlador para interactuar con los clientes. Le llamamos Store.

$ rails generate controller Store index
  create  app/controllers/store_controller.rb
   route  get "store/index"
  invoke  erb
  create    app/views/store
  create    app/views/store/index.html.erb
  invoke  test_unit
  create    test/controllers/store_controller_test.rb
  invoke  helper
  create    app/helpers/store_helper.rb
  invoke    test_unit
  create      test/helpers/store_helper_test.rb
  invoke  assets
  invoke    coffee
  create      app/assets/javascripts/store.js.coffee
  invoke    scss
  create      app/assets/stylesheets/store.css.scss

Haremos que este index sea la ruta root del sitio:

[rails40/depot_d/config/routes.rb]

Depot::Application.routes.draw do
  get "store/index"
  resources :products

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

  # You can hav the root of your site routed with "root"
  root 'store#index', as: 'store'
  #...
end

[rails40/depot_d/app/controllers/store_controller.rb]

class StoreController < ApplicationController
  def index
    @products = Product.order(:title)
  end
end

[rails40/depot_d/app/views/store/index.html.erb]

<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>

<h1>Your Pragmatic Catalog</h1>

<% @products.each do |product| %>
  <div class="entry">
    <%= image_tag(product.image_url) %>
    <h3><%= product.title %></h3>
    <%= sanitize(product.description) %>
    <div class="price_line">
      <span class="price"><%= product.price %></span>
    </div>
  </div>
<% end %>

El uso del método sanitize() nos permite agregar en forma segura estilos HTML para realzar la descripción. Note que esta decisión abre un potencial agujero de seguridad (http://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29), pero como la descripción de productos es realizada por nuestro personal pensamos que el riesgo en mínimo.

Agreguemos una planilla de estilos:

[rails40/depot_d/app/assets/stylesheets/store.css.scss]

// Place all the styles related to the Store controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com

.store {
  h1 {
    margin: 0;
    padding-bottom: 0.5em;
    font: 150% sans-serif;
    color: #226;
    border-bottom: 3px dotted #77d;
  }

  /* An entry in the store catalog */
  .entry {
    overflow: auto;
    margin-top: 1em;
    border-bottom: 1px dotted #77d;
    min-height: 100px;
  
    img {
      width: 80px;
      margin-right: 5px;
      margin-bottom: 5px;
      position: absolute;
    }

    h3 {
      font-size: 120%;
      font-family: sans-serif;
      margin-left: 100px;
      margin-top: 0;
      margin-bottom: 2px;
      color: #277;
    }

    p, div.price_line {
      margin-left: 100px;
      margin-top: 0.5em;
      margin-bottom: 0.8em;
    }

    .price {
      color: #44a;
      font-weight: bold;
      margin-right: 3em;
    }
  }
}

##8.2 Iteración C2: Agregando un layout de página

Modificaremos el layout de la aplicación para definir un banner y un sidebar

[rails40/depot_e/app/views/layouts/application.html.erb]

<!DOCTYPE html>
<html>
<head>
  <title>Pragprog Books Online Store</title>
  <%= styelesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body class="<%= controller.controller_name %>">
  <div id="banner">
    <%= image_tag("logo.png") %>
    <%= @page_title || 'Pragmatic Bookshelf" %>
  </div>
  <div id="columns">
    <div id="side">
      <ul>
        <li><a href="http://www...">Home</a></li>
        <li><a href="http://www.../faq">Questions</a></li>
        <li><a href="http://www.../news">News</a></li>
        <li><a href="http://www.../contact">Contact</a></li>
      </ul>
    </div>
    <div id="main">
      <%= yield %>
    </div>
  </div>
</body>
</html>

Para hacer que esto funcione, primero renombre el archivo application.css a application.css.scss.

[rails40/depot_e/app/assets/stylesheets/application.css.scss]

/*
 * This is a manifest file that'll be compiled into application.css, which will
 * include all the files listed below
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets,
 * vendor/assets/stylesheets, o app/assets/stylesheets of plugins, if any,
 * can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear
 * at the top of the compiled file, but it's generally better to create a new
 * file per style scope.
 *
 *= require_self
 *= require_tree .
 */

#banner {
  background: #9c9;
  padding: 10px;
  border-bottom: 2px solid;
  font: small-caps 40px/40px "Times New Roman", serif;
  color: #282;
  text-align: center;

  img {
    float: left;
  }
}

#notice {
  color: #000 !important;
  border: 2px solid red;
  padding: 1em;
  margin-bottom: 2em;
  background-color: #f0f0f0;
  font: bold smaller sans-serif;
}

#columns {
  background: #141;

  #main {
    margin-left: 17em;
    padding: 1em;
    background: white;
  }

  #side {
    float: left;
    padding: 1em 2em;
    width: 13em;
    background: #141;

    ul {
      pading: 0;
      li {
        list-style: none;
        a {
          color: #bfb;
          font-size: small;
        }
      }
    }
  }
}

Este archivo manifiesto automáticamente incluirá todas las planillas de estilo disponibles en este directorio y en todos los subdirectorios. Esto se logra vía la directiva require_tree.

Podríamos listar a cambio todas las planillas de estilo individuales que deseamos sean linkeadas por stylesheet_link_tag, pero ya que estamos en el layout de la aplicación completa y éste está seteado para cargar todas las planillas, lo dejaremos así por ahora.

Una vez más, hacemos uso de Sass, lo que se habilita al cambiarle el nombre. Por ejemplo, hay un selector img anidado dentro del selector #banner.

##8.2 Iteración C3: Usando un helper para formatear el precio

Ruby provee una función sprintf() que puede ser usada para formatear precios

<span class="price"><%= sprintf("$%0.02f", product.price) %></span>

Pero Rails tiene un helper llamado number_to_currency()

<span class="price"><%= number_to_currency(product.price) %></span>

##8.4 Iteración C4 Prueba funcional de los controladores

Primero veamos que genera Rails para nosotros

[rails40/depot_d/test/controllers/store_controller_test.rb]

require 'test_helper'

class StoreControllerTest < ActionController::TestCase
  test "should get index" do
    get: index
    assert_response :success
  end
end

Pero verificaremos además que la respuesta contiene nuestro layout, la información de nuestro producto y nuestro número formateado.

[rails40/depot_d/test/controllers/store_controller_test.rb]

require 'test_helper'

class StoreControllerTest < ActionController::TestCase
  test "should get index" do
    get: index
    assert_response :success
    assert_select '#columns #side a', minimum: 4
    assert_select '#main .entry', 3
    assert_select 'h3', 'Programming Ruby 1.9'
    assert_select '.price', /\$[,\d]+\.\d\d/
  end
end

Los selectores que comienzan con # coinciden con atributos id, y los que comienzan con . coinciden con atributos de clase, finalmente los que no tienen prefijo coinciden con el nombre de los elementos.

Estas afirmaciones están basadas en los datos que pusimos en nuestra fixture.

El tipo de prueba que realiza assert_select() depende del valor que tome su segundo parámetro. Si es un número, lo tratará como una cantidad. Si es un string, lo tratará como el resultado esperado. Una expresión regular es para verificar formatos.

Más información acerca de assert_select() en http://api.rubyonrails.org/classes/ActionDispatch/Assertions/SelectorAssertions.html

solo falta correr el test

$ rake test:controllers

##8.5 Iteración C5: Caching de resultados parciales

Encender el cache

[rails40/depot_e/config/environments/development.rb]

config.action_controller.perform_caching = true

Necesitamos volver a renderear sólo si un producto ha cambiado. Para ello creamos una funcion

[rails40/depot_e/app/models/product.rb]

def self.latest
  Product.order(:updated_at).last
end

Marcamos la sección en nuestro template que hay que actualizar si cualquier producto cambia, y dentro de esta sección una subsección que necesitamos con el fin de actualizar cualquier producto que haya cambiado.

[rails40/depot_e/app/views/store/index.html.erb]

<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>

<h1>Your Pragmatic Catalog</h1>

<% cache ['store', Product.latest] do %>
  <% @products.each do |product| %>
    <% cache ['entry', product] do %>
      <div class="entry">
        <%= image_tag(product.image_url) %>
        <h3><%= product.title %></h3>
        <%= sanitize(product.description) %>
        <div class="price_line">
          <span class="price"><%= number_to_currency(product.price) %></span>
        </div>
      </div>
    <% end %>
  <% end %>
<% end %>

Una vez estamos satisfechos con el trabajo del cache lo desactivamos en desarrollo

[rails40/depot_e/config/environments/development.rb]

config.action_controller.perform_caching = false
⚠️ **GitHub.com Fallback** ⚠️