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
endPero 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
endLos 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 = trueNecesitamos 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
endMarcamos 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