• No se han encontrado resultados

Rails-5 El carrito de la compra

N/A
N/A
Protected

Academic year: 2021

Share "Rails-5 El carrito de la compra"

Copied!
13
0
0

Texto completo

(1)

Rails-5

Rails-5

El carrito de la compra

El carrito de la compra

Bibliografía:

Agile Web Development with Rails, 3rd ed.

(capítulo 8)

El carrito de la compra - 2

Objetivos

Objetivos

Introducir el concepto de sesión

Aprender a gestionar sesiones en Rails

Usar modelos no soportados por una Base de

Datos

Mejorar la robustez de la aplicación

Generar avisos mediante el flash

Generar eventos de logging

(2)

El carrito de la compra - 3 ©GSyC-2009

Sesiones en HTTP

Sesiones en HTTP

Cada usuario navega por el catálogo de productos

Va añadiendo al carrito los productos que desea

Al terminar la

sesión

, paga los productos del carrito

Por tanto, la aplicación tiene que recordar los contenidos

que el usuario fue añadiendo al carrito durante la sesión

Problema

: HTTP es un protocolo sin estado (

stateless

)

No implementa el concepto de sesión: no hay relación entre

las diferentes peticiones que envía un navegador: el servidor no relaciona las peticiones

Solución

: hay que implementar en el servidor las

sesiones, para que cuando llegue una nueva petición se

pueda relacionar con el estado que la aplicación

guarda

para esa sesión

Mecanismos para implementar sesiones:

Reescritura de URLs: el identificador de sesión se almacena en la propia sesión

Uso de galletitas (cookies)

Sesiones mediante

Sesiones mediante

cookies

cookies

Rails implementa las sesiones mediante cookies

Por tanto ¡los navegadores tienen que tener habilitado el

uso de las cookies!

Una cookie es estado con nombre que intercambian navegador

y servidor

El servidor manda cookies en las cabeceras de los mensajes

de respuesta que envía al navegador: nombre-cookie:

valor-cookie

El navegador guarda las cookies en disco

Cuando el navegador vuelve a visitar un sitio web, envía en

las cabeceras de sus mensajes las cookies que le envió ese sitio web

La aplicación puede utilizar una cookie para implementar las

sesiones:

En función del valor de una cookie, puede relacionar varias peticiones

(3)

El carrito de la compra - 5 ©GSyC-2009

El hash

El hash

session

session

 Rails proporciona la funcionalidad de las sesiones mediante

una estructura de tipo hash denominada session, disponible para cada usuario

Las parejas clave/valor almacenadas en una acción en

dicha hash están disponibles para ser consultadas en acciones posteriores invocadas por el mismo navegador

 Implementaremos el carrito de la compra usando el hash session

 Por omisión, Rails almacena los contenidos del hash

session en cabeceras set-cookie que envía al navegador

Problema: ¿qué ocurre si en esos datos hay información

confidencial o sensible...?

Solución: Rails permite almacenar la sesión en una tabla

de la BD de la aplicación, y enviar como cookie sólo un identificador para acceder a dicha tabla

El carrito de la compra - 6

 Para almacenar la sesión en primer lugar creamos una migración que contenga la definición de la tabla de sesiones:

 Aplicamos la migración para añadir la tabla sessions al esquema de nuestra BD:

 Configuramos Rails para almacene la sesión en la BD en vez de almacenar todo en cookies:

En config/environment.rb descomentamos la última de estas

3 líneas:

 Descomentamos el secreto de app/controllers/application.rb

para dificultar la generación de peticiones falsificadas:

 Por último rearrancamos el servidor (ruby script/server)

Almacenamiento de sesiones en la

Almacenamiento de sesiones en la

BD

BD

depot> rake db:sessions:create exists db/migrate

create db/migrate/20080601000004_create_sessions.rb

depot> rake db:migrate

# Use the database for sessions instead of the file system # (create the session table with 'rake db:sessions:create')

config.action_controller.session_store = :active_record_store

(4)

El carrito de la compra - 7 ©GSyC-2009

Relación entre el carrito y las

Relación entre el carrito y las

sesiones

sesiones

La primera vez que llegue una petición de un navegador

añadiremos un nuevo objeto

cart

a la sesión, con

clave

:

cart

(suponemos que existe una clase

Cart

)

Cuando llegen sucesivas peticiones de la misma sesión,

recuperamos el objeto

cart

de la sesión

Añadimos un método privado al controlador

app/controllers/store_controller.rb

¿Por qué privado?

porque NO queremos que sea una acción del

controlador

private

def find_cart

session[:cart] ||= Cart.new

end

Creación del carrito

Creación del carrito

El carrito contiene datos e implementa parte de la lógica de la aplicación; por lo tanto ni es una vista ni un controlador: es un modelo

 Hasta ahora nuestro único modelo (product.rb) se correspondía con una tabla de la BD. Pero no todos los modelos tienen por qué corresponderse con una tabla de la BD.

NOTA: El carrito se almacena en la sesión, y la sesión se almacena en

la BD. Pero el carrito no es una tabla de la BD

 Ahora no se crea el fichero con la clase del modelo a través de una migración. Al no estar el carrito directamente en la BD

tenemos que crear el modelo escribiendo directamente la clase

Cart en app/models/cart.rb: class Cart attr_reader :items def initialize @items = [] end def add_product(product) @items << product end end

(5)

El carrito de la compra - 9 ©GSyC-2009

Relación vista/controlador

Relación vista/controlador

En la vista

app/views/store/index.html.erb

habíamos

añadido a cada producto un botón asociado a la acción

add_to_cart

(acción que aún no hemos escrito)

<%= button_to "Add to Cart", :action => :add_to_cart, :id => product.id %>

A veces se escribe product en vez de product.id. Es lo

mismo: en este ámbito product es una abreviatura de

product.id

 product.id es el campo que usa Rails para identificar un

objeto del modelo. En el caso del producto corresponde con la columna id del producto en la BD (la clave primaria)

 La vista le comunica así al controlador qué producto es el

que se quiere añadir

La acción add_to_cart sabrá así exactamente qué añadir

al carrito

El carrito de la compra - 10

Relación vista/controlador

Relación vista/controlador

Escribimos la acción

add_to_cart

del controlador

app/

controllers/store_controller.rb

Ojo, tiene que ser public porque es una acción:

def add_to_cart @cart = find_cart

product = Product.find(params[:id]) @cart.add_product(product)

end

find_cart es el método privado que ya habíamos escritoparams almacena los parámetros pasados por el

navegador a la acción

 Es un convenio que el parámetro :id sea el id (clave primaria si está en la BD) del objeto que tiene que utilizar la acción

 params[:id] es como se recibe el :id => product.id especificado en el botón que aparece en la vista junto a cada producto

 La url que se invoca al pulsar el botón es esta:

http ://localhost:3000/store/add_to_cart/XX

(6)

El carrito de la compra - 11 ©GSyC-2009

Relación vista/controlador

Relación vista/controlador

Si probamos pulsando el botón de un producto aún no

funciona:

Tenemos que añadir una vista para esta acción:

app/views/store/add_to_cart.html.erb

<h2>Your Pragmatic Cart</h2> <ul>

<% for item in @cart.items %> <li><%= h(item.title) %></li>

<% end %>

</ul>

Ahora sí: si recargamos en el navegador (reenviando

por tanto la acción del botón para el producto 2)

vemos el carrito:

Si vamos de nuevo a la página principal y añadimos nuevos productos los veremos en esta vista

Mejora del carrito

Mejora del carrito

Si un usuario compra 10

unidades de un mismo

producto en la vista

aparecen 10 líneas

Mejora: que salga una sóla línea de ese

producto, indicando que quiere 10 unidades del mismo

Lo que haremos es

guardar cada producto

una sóla vez en el

carrito, junto a la

cantidad

Creamos para ello un

nuevo modelo, la clase

CartItem

app/models/cart_item.rb

class CartItem attr_reader :product,:quantity def initialize(product) @product = product @quantity = 1 end def increment_quantity @quantity += 1 end def title @product.title end def price @product.price * @quantity end end

(7)

El carrito de la compra - 13 ©GSyC-2009

Mejora del carrito

Mejora del carrito

app/models/cart.rb

def add_product(product)

current_item = @items.find {|item| item.product == product} if current_item current_item.increment_quantity else @items << CartItem.new(product) end end

Ahora tenemos que modificar

Cart#add_product

Y a continuación modificamos la vista de

add_to_cart

para que extraiga la información del carrito mejorado

app/views/store/add_to_cart.html.erb

<h1>Your Pragmatic Cart</h1> <ul>

<% for cart_item in @cart.items %>

<li><%= cart_item.quantity %> &times; <%= cart_item.title) %></li>

<% end %>

</ul>

El carrito de la compra - 14

Mejora del carrito

Mejora del carrito

Probamos la aplicación y… no funciona:

El problema es que la sesión está almacenada en la

BD y los items del carrito son objetos

Product

 ¡pero add_product, llamado desde add_to_cart

, espera

(8)

El carrito de la compra - 15 ©GSyC-2009

Mejora del carrito

Mejora del carrito

Solución: limpiar la tabla de sesiones en la BD...

depot>

rake db:sessions:clear

y comenzar una nueva sesión desde la página del

catálogo pulsando Atrás y Recargar en el navegador.

Si no se hace así, aparecerá un error de ActionController::InvalidAuthenticityToken

al tratar de actuar sobre una sesión que ya no existe)

Mejora del carrito

Mejora del carrito

Problema: si la aplicación estaba ya en producción,

habríamos vaciado las sesiones, y por tanto los

carritos, de todos nuestros clientes

Solución: Sería mejor no guardar información del

modelo en la sesión. Así si lo cambiamos no hay que

inicializar las sesiones

Para hacerlo podríamos hacer (aunque no lo haremos en

esta aplicación):

 que el modelo Cart fuese un modelo Active Record (es

decir, respaldado por una tabla en la BD)

 almacenar en la sesión simplemente el id de un Cart que

está en la BD

 Cuando llegue la petición se extraería el id del carrito de

la sesión y a continuación recuperaríamos el carrito de la BD

(9)

El carrito de la compra - 17 ©GSyC-2009

Mejora de la robustez:

Mejora de la robustez:

flash, log

flash, log

Si se envían peticiones mal formadas la

aplicación puede mostrar errores que pueden

dar lugar a fallos de seguridad, y además no

queda bien que la aplicación “se rompa”:

 Al pedirle un id de producto inexistente, se eleva una excepción en la acción add_to_cart del controlador  Podríamos ignorarla, pero entonces no detectaríamos errores El carrito de la compra - 18

Mejora de la robustez:

Mejora de la robustez:

flash, log

flash, log

El flash:

El flash es una estructura de datos tipo hash en la que se

pueden almacenar datos mientras procesamos una petición (se guarda en la sesión)

Sus contenidos están disponibles en la siguiente petición

de la misma sesión

Ejemplo de uso:

 Si nos piden un id de producto inválido en la acción add_to_cart

almacenaremos una indicación de error en el flash

 Luego redirigiremos el navegador a la acción index del catálogo  Finalmente, desde la vista de index mostraremos en la pantalla los

contenidos del flash

La información almacenada en el flash está disponible

(10)

El carrito de la compra - 19 ©GSyC-2009

Mejora de la robustez:

Mejora de la robustez:

flash, log

flash, log

Mejoremos la robustez de la acción

add_to_cart

del

controlador

Cuando se eleve la excepción porque no exista un id de

producto:

Registraremos la incidencia en el log

Guardaremos un mensaje para el usuario en el flash

 Redirigiremos el navegador a la página principal del

catálogo para no mostrar el error

def add_to_cart begin product = Product.find(params[:id]) @cart = find_cart @cart.add_product(product) rescue ActiveRecord::RecordNotFound

logger.error("Attempt to access invalid product #{params[:id]}") redirect_to_index("Invalid product") end end

Mejora de la robustez:

Mejora de la robustez:

flash, log

flash, log

Al cargar de nuevo la URL con un

id

de producto

inexistente como

wibble

aparecerá la siguiente línea

en

log/development.log

Parameters: {"action"=>"add_to_cart", "id"=>"wibble", "controller"=>"store"}

Product Load (0.000427) SELECT * FROM products WHERE (products.id = 'wibble') LIMIT 1

Attempt to access invalid product wibble

Redirected to http://localhost:3000/store/index Completed in 0.00522 (191 reqs/sec) . . .

Processing StoreController#index ... : :

Rendering within layouts/store Rendering store/index

private

def redirect_to_index(msg)

flash[:notice] = msg

redirect_to :action => :index

end

 El método redirect_to_index no es una acción (por eso

lo hacemos private), sino un método auxiliar del

controlador en el que se almacena el error en el flash bajo la clave :notice, y se redirige a la página principal:

(11)

El carrito de la compra - 21 ©GSyC-2009

Mejora de la robustez:

Mejora de la robustez:

flash, log

flash, log

Para que aparezca lo almacenado en el flash hay que modificar la vista index o, mejor aún, el layout del controlador:

app/views/layouts/store.html.erb

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >

<html> <head>

<title>Pragprog Books Online Store</title>

<%= stylesheet_link_tag "depot" , :media => "all" %>

</head>

<body id="store"> <div id="banner">

<%= image_tag("logo.png" ) %>

<%= @page_title || "Pragmatic Bookshelf" %> </div>

<div id="columns"> <div id="side">

<a href="http://www....">Home</a><br />

<a href="http://www..../faq">Questions</a><br /> <a href="http://www..../news">News</a><br />

<a href="http://www..../contact">Contact</a><br />

</div>

<div id="main">

<% if flash[:notice] -%>

<div id="notice"><%= flash[:notice] %></div>

<% end -%> <%= yield :layout %> </div> </div> </body> </html> El carrito de la compra - 22

Mejora de la robustez

Mejora de la robustez

Y añadimos a la hoja de estilo cómo se mostrará el flash:

public/stylesheets/depot.css

#notice {

border: 2px solid red; padding: 1em;

margin-bottom: 2em;

background-color: #f0f0f0; font: bold smaller sans-serif; }

Finalmente aparece el flash en la página a la que se

redirecciona al navegador cuando se pide una URL con un

(12)

El carrito de la compra - 23 ©GSyC-2009

Últimos retoques: vaciar

Últimos retoques: vaciar

carrito

carrito

Para terminar el carrito, vamos a añadir un botón para

vaciar el carrito en la vista que lo muestra

app/views/store/add_to_cart.html.erb

<h1>Your Pragmatic Cart</h1> <ul>

<% for cart_item in @cart.items %>

<li><%= cart_item.quantity %> &times; <%= h(cart_item.title) %></li>

<% end %>

</ul>

<%= button_to "Empty cart", :action => :empty_cart %>

def empty_cart

session[:cart] = nil

redirect_to_index("Your cart is currently empty”)

end

Y añadimos la acción correspondiente al controlador

que elimina el carrito de la sesión, y luego llama a

redirect_to_index

para apuntar el mensaje en el

flash y redirigir a la página principal al navegador

Últimos retoques: estética

Últimos retoques: estética

 Por último, retocamos la vista del carrito cambiando la lista HTML por una tabla : app/views/store/add_to_cart.html.erb

<div class="cart-title">Your Cart</div> <table>

<% for cart_item in @cart.items %> <tr> <td><%= cart_item.quantity %>&times;</td> <td><%= h(cart_item.title) %></td> <td class="item-price"><%= number_to_currency(cart_item.price) %></td> </tr> <% end %> <tr class="total-line"> <td colspan="2">Total</td> <td class="total-cell"><%= number_to_currency(@cart.total_price) %></td> </tr> </table>

<%= button_to "Empty cart", :action => :empty_cart %>

def total_price

@items.sum { |item| item.price } end

Añadimos unos retoques en la hoja de estilo

(http://media.pragprog.com/titles/rails3/code/depot_i/public/stylesheets/depot.css)

 Añadimos el método auxiliar al modelo del carrito

(13)

El carrito de la compra - 25 ©GSyC-2009

Aspecto final

Aspecto final

El carrito de la compra - 26

Ejercicios

Ejercicios

Añadir una nueva variable contador a la sesión

para controlar el número de veces que un

usuario ha accedido a la acción index.

Mostrar el contador al principio de la página del

catálogo. Puede venirte bien el helper

pluralize

Ejemplo de uso:

<%= pluralize(1, "person") %> but <%= pluralize(2, "person") %>

1 person but 2 people

Inicializar el contador a cero cuando el usuario

añada algo al carrito

Cambiar cómo se muestra el contador, de forma

que sólo aparezca al principio de la página del

catálogo si es mayor que 5.

Referencias

Documento similar

Cedulario se inicia a mediados del siglo XVIL, por sus propias cédulas puede advertirse que no estaba totalmente conquistada la Nueva Gali- cia, ya que a fines del siglo xvn y en

Para ello, trabajaremos con una colección de cartas redactadas desde allí, impresa en Évora en 1598 y otros documentos jesuitas: el Sumario de las cosas de Japón (1583),

En junio de 1980, el Departamento de Literatura Española de la Universi- dad de Sevilla, tras consultar con diversos estudiosos del poeta, decidió propo- ner al Claustro de la

Habiendo organizado un movimiento revolucionario en Valencia a principios de 1929 y persistido en las reuniones conspirativo-constitucionalistas desde entonces —cierto que a aquellas

The part I assessment is coordinated involving all MSCs and led by the RMS who prepares a draft assessment report, sends the request for information (RFI) with considerations,

De hecho, este sometimiento periódico al voto, esta decisión periódica de los electores sobre la gestión ha sido uno de los componentes teóricos más interesantes de la

 Para recibir todos los números de referencia en un solo correo electrónico, es necesario que las solicitudes estén cumplimentadas y sean todos los datos válidos, incluido el

Volviendo a la jurisprudencia del Tribunal de Justicia, conviene recor- dar que, con el tiempo, este órgano se vio en la necesidad de determinar si los actos de los Estados