Diapositivas correspondientes a la parte del framework Ruby On Rails del curso de extensión universitaria "Desarrollo Web Avanzado", celebrado en la Escuela Universitaria de Ingeniería Informática de Oviedo
3. Un poco de Historia
• David Heinemeier Hansson ( 37 signals )
2004 (1.0)
• Agosto 2006 Apple anuncia su inclusión en
Mac OSX 10.5 (v1.2.3)
• Última versión estable 2.3.5
• v3.0.0 en preview release (Fusión con Merb)
4. Filosofía
• Full stack - Da soporte íntegro al desarrollo Web.
• Siguelos principios Convention over Configuration (CoC) y Don’t
Repeat Yourself (DRY) (se sirve de las capacidades reflectivas
del ruby para esto).
5. Visión arquitectónica (MVC) M ODELS , V IEWS , AND C ONTROLLERS 25
!12*(.%&*1%&)0%1*&34&%#
"1'()#*(++&*1-)#&*"5#%1.-#617(0&+
#1'()#*(++&*1-)8(9&%18-&.
!
'()#*(++&* $1,-&.1*&)0&*%1)&:#1$*(.%&*1%5*&&)
$ # "
,-&. /(0&+ !"#"$"%&
M ODELS , V IEWS , AND C ONTROLLERS 26
! B2/#;:C
Figure 2.1: The Model-View-Controller Architecture
!'(##)*++,-./01+%#20&+"334#245"0#+678
" "'B2/#;:C'F;:3%'A#20&'52:#0211&0
#'92:#0211&0';:#&0"5#%'<;#(',23&1
$'92:#0211&0';:=2>&%'=;&<
be a view that displays product information on a catalog page and another set
A#20& %&?;&<'0&:3&0%':&@#'$02<%&0'%50&&:
of views used by administrators to add and edit products.
92:#0211&0
Controllers orchestrate $ application. Controllers receive events from the
% the #
outside world (normally user input), interact with the model, and display an
appropriate view to the user.
!;%)1"- D5#;=&'
This triumvirate—the model, view, and controller—together form an architec-
!"#"$"%&
9"0# B&5203
ture known as MVC. Figure 2.1 shows MVC in abstract terms.
?;&< E23&1
MVC was originally intended for conventional GUI applications, where devel-
opers found the separation of concerns led to far less coupling, which in turn
Figure 2.2: Rails and MVC
7. Visión arquitectónica
(componentes)
Ruby on rails
Core Active resource
Action pack
Active support
plugin
plugin
Action controller Action mailer
...
Action view
Active record Action webservice
14. URLs, rutas, controladores y acciones
Para escribir nuestra primera aplicación dinámica, tenemos
que definir un controlador y una vista.
http://railsapi.com/doc/rails-v2.3.5/
15. Invocando a una acción del
controlador
http://127.0.0.1:3000/saludador/hola
aplicación controlador acción
La convención dice que tras invocar una acción, se redirigirá a la vista
controlador/accion dentro del directorio views...
16. Completando la plantilla
• Lasplantillas html se definen en un lenguaje llamado ERB. Muy
similar a otros lenguajes de plantillas, como JSP.
• <% expresión %> encierran expresiones ruby
• <%= expresión %> el resultado de su evaluación se inyecta
en el html de salida.
• Lasvariables de instancia definidas en los controladores están
disponibles en la plantilla aun siendo privadas (introspección).
• Los
métodos definidos en app/helpers/controlador
también están disponibles en la plantilla.
18. Desarrollo incremental e iterativo
• Crearemos una tienda on-line de
forma incremental
• Abordaremos el desarrollo de un
aspecto funcional en cada iteración
• Alfinal de cada iteración tendremos
algo que funciona.
19. It0 - Creación de la aplicación
• Hemos visto como crear una aplicación: rails app_name
• Para seguir el ritmo usaremos subversion, al final de cada
iteración, actualizaremos a la revisión finalizada.
• Inicialmente hacemos un checkout de la versión inicial.
• workspace$ svn co file:///home/${USUARIO}/svnrepos/
skateshop/tags/it0 skateshop
• Abrimos con netbeans la aplicación.
20. Some folks like to mock up web application page flows using Photoshop, Word,
¿En qué consiste? Comprador
or (shudder) HTML. I like using a pencil and paper. It’s quicker, and the cus-
tomer gets to play too, grabbing the pencil and scribbling alterations right on
the paper.
21. ¿En qué consiste? Vendedor
W HAT D EPOT D OES 66
Figure 5.2: Flow of Seller Pages
22. It1. Gestionar los productos
• En esta iteración nos encargaremos del alta, baja y modificación de los
productos a vender.
• Tareas:
• A) Preparar las bases de datos (MySQL o SQLite3)
• B) Crear el modelo, el controlador y vistas de mantenimento (scaffolding)
• C) Añadir nuevos campos
• D) Añadir validaciones
• E) Darle “un toque” de estilo
23. It1. B) Scaffolding
skateshop$ script/generate scaffold product
title:string description:text image_url:string
• el controlador: products_controller.rb (con acciones de
mantenimiento)
• las vistas de mantenimiento: una por cada acción (salvo create) y layout
común layouts/products.html.erb
• el modelo: products.rb y su migración inicial
• una ruta nueva en routes.rb
• un helper: products_helper.rb
• tests (unitarios y funcionales)
24. It1. C) Añadiendo nuevos campos
skateshop$ script/generate migration
add_price_to_product price:decimal
class AddPriceToProduct < ActiveRecord::Migration
def self.up
El nombre importa! si la add_column :products, :price, :decimal,
migración empieza por :precision=>8, :scale=>2, :default=>0
create, creará una tabla, si end
empieza por add, añadirá
def self.down
una columna. remove_column :products, :price
end
end
skateshop$ rake db:migrate
Es preciso actualizar las vistas, ya que el scaffolding no es
dinámico (desde rails 2)
25. It1. D) Añadiendo validaciones
Un producto tiene que cumplir que tiene un título, y que no
haya más productos con el mismo; una descripción y una url
válida para la imagen. Además tenemos que comprobar que el
precio es positivo.
class Product < ActiveRecord::Base
validates_presence_of :title, :description, :image_url
validates_uniqueness_of :title
validates_numericality_of :price
validate :precio_tiene_que_ser_por_lo_menos_un_centimo, :url_tiene_que_ser_una_imagen
private
def precio_tiene_que_ser_por_lo_menos_un_centimo
errors.add(:price, "tiene que ser por lo menos un centimo") unless price >= 0.01
end
def url_tiene_que_ser_una_imagen
unless /.(gif|jpg|png)$/i === image_url
errors.add(:url,"tiene que ser una url para gif, jpg o png")
end
end
end
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
26. It1. E) Dandole un toque de estilo
Necesitamos datos de prueba que se generen de forma
automática. Instalamos random_data y decimos a rails que la
use skateshop$ sudo gem install random_data
Creamos una migración que cargue los datos
class AddTestData < ActiveRecord::Migration
def self.up
Product.delete_all
images=["tabla.jpg","pantalones.jpg","camiseta.jpg","zapatillas.jpg","gorra.jpg"]
images.each do |image|
p=Product.new :title=>
p.title=image.gsub(".jpg"," ").capitalize
p.description=Random.paragraphs(2)
p.image_url="./"+image
p.price=Random.number(100)
p.save
end
end
def self.down
Products.delete_all
end
end
27. It1. E) Dandole un toque de estilo
El listado scaffolded no se adecua a cómo queremos que se
visualicen los datos. Modifiquémoslo y añadamos una hoja
de estilos
<div id="product-list">
<h1>Listing products</h1>
<table cellpadding="5" cellspacing="0">
<% for product in @products %>
<tr valign="top" class="<%= cycle('par', 'impar') %>">
<td>
<%= image_tag product.image_url, :class => 'list-image' %>
</td>
<td width="60%">
<span class="list-title"><%=hproduct.title %></span><br />
<%=h truncate(product.description.gsub(/<.*?>/,''), 80) %>
</td>
<td class="list-actions">
<%= link_to 'Show', product %>
<%= link_to 'Edit', edit_product_path(product) %>
<%= link_to 'Destroy', product, :confirm => "Seguro?", :method=>:delete %>
</td>
</tr>
<% end %>
</table>
</div>
28. It 2. Confeccionar el catálogo
• En esta iteración nos encargaremos de confeccionar el catálogo, creando un
controlador y un conjunto de vistas personalizadas. Veremos cómo usar helpers
y disparar acciones desde la vista.,
• Tareas:
• A) Crear el catálogo
• B) Diseñar el layout
29. It2 A) Crear el catálogo
• El catálogo se mostrará en la home del sitio. class Product < ActiveRecord::Base
def self.method_missing sym, args=nil
• generamos el controlador store con la pattern=/find_sorted_by_/i
if sym.to_s =~ pattern
acción index (desde Netbeans para find :all, :order=> sym.to_s.gsub
variar) (pattern,"").to_sym
else
super.method_missing(sym, *args)
• modificamos routes.rb para indicar que end
end
el home es la acción index del #resto de la clase
controlador y borrar el fichero end
index.html. [check]
class StoreController <
ApplicationController
• En la acción del controlador recuperamos
def index
todos los productos, para pasarlos a la vista. @products=Product.find_sorted_by_title
Lo haremos à-la rails: mediante reflexión end
computacional.
end
30. It2 A) Crear el catálogo
<h1>Product Catalog</h1>
<div id="product-catalog">
<% @products.each do |product| %>
<div class="product <%=cycle("even","odd")%>">
<div id="product-image">
<%= image_tag product.image_url, :alt=>product.title%>
</div>
• La vista mostrará el
<div id="product-description">
catálogo en forma de <h3 class="product-title"><%=h product.title %></h3>
listado <p class="product-description"><%=h truncate
(product.description,200,"...") %></p>
<span class="price"><%=h product.price%></span>
</div>
</div>
<% end %>
</div>
<% content_for :extra do %>
Powered by Ruby On Rails
<%end%>
31. It2 B) Diseñar el layout
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html>
<head>
<title>Skateshop <%= @page_title && ":: "+@page_title %> </title>
<%= stylesheet_link_tag "shop", :media => "all" %></head>
• La web necesita de un <body id="store">
diseño consistente <div id="banner">
<%= image_tag("skate_logo.png") %>
<%= @page_title || "Skateshop" %>
• Debemos de crear un <span id="extra"><%= yield :extra %></span>
</div>
layout (una plantilla) cuyo <div id="columns">
contenido variará en <div id="side">
función de la vista concreta <a href="http://www....">Home</a><br />
<a href="http://www..../faq">Questions</a><br />
que se esté renderizando. <a href="http://www..../news">News</a><br />
<a href="http://www..../contact">Contact</a><br />
</div>
<div id="main">
<%= yield :layout %>
</div>
</div>
</body>
</html>
32. It 3. Crear el carrito
• En esta iteración veremos cómo gestionar sesiones desde el framework,
haciéndolas persistentes. Aplicaremos lo aprendido a la creación de un carrito
de la compra. Tareas
• A) Añadir productos al carrito
• B) Configurar la gestión de Sesiones
• C) Confeccionar el carrito
• D) Gestionar errores
33. It3 A) Añadir productos al carrito
Queremos poder añadir cada producto del catálogo al
carrito.
Podemos usar el built-in helper button_to(value,args),
similar a link_to
<%= button_to "Add to Cart", :action => :add_to_cart, :id => product %>
Los helpers built-in que pueden ser usados en las vistas
están definidos en las clases
ActionView::Helpers::*Helper
34. It 3. Gestión de sesiones
• HTTP es stateless, pero se usan mecanismos. Para simular estados se
usan mecanismos como URL rewriting, hidden input fields y cookies.
• Rails usa cookies para identificar diferentes peticiones asociadas a la
misma sesión de navegación.
• El objeto session nos abstrae de este mecanismo (se comporta
como un hash).
• Por defecto session persiste en disco. Pero hay otros mecanismos:
persistencia en memoria, en base de datos, DRb...
35. It 3 B). Configurar la gestión de
sesiones
Usaremos persistencia de sesiones en base de datos.
Rails ofrece una tarea rake para esto.
~/skateshop$ rake db:sessions:create
&& rake db:migrate
Además tendremos que indicar en config/environment.rb
que queremos usar este tipo de persistencia, ya que el
tipo por omisión es en ficheros de texto en disco.
config.action_controller.session_store = :active_record_store
Los cambios en otros directorios distintos de app y public
requieren reiniciar el servidor. Reiniciamos
36. It3 C) Confeccionar el carrito
Necesitamos definir una acción en el controlador para obtener el carrito de
sesión y añadirle productos.
class StoreController < ApplicationController
def index
@products=Product.find_sorted_by_title
end
def add_to_cart
@cart=find_cart
@cart.add_product Product.find(params[:id])
end
def empty_cart
session[:cart]=nil
flash[:notice]="Your car is empty"
redirect_to :action => :index
end
private
def find_cart
session[:cart] ||= Cart.new
end
end
37. It3 C) Confeccionar el carrito
Y definir la propia clase carrito, que es un almacén de productos.
class Cart
class CartItem
attr_reader :product, :quantity
attr_reader :items
def initialize(product)
def initialize
@product=product
@items=[]
@quantity=1
end
end
def add_product(product)
def title
item = items.find { |item| item.title==product.title }
product.title
if item
end
item.increment_quantity
else
def price
@items << CartItem.new(product)
product.price * quantity
end
end
end
def increment_quantity
def total_price
@quantity+=1
@items.sum{ |item| item.price }
end
end
end
end
38. It3 C) Confeccionar el carrito
Además habrá que definir una vista que nos muestre el carrito.
<div class="cart-title">Your Cart</div>
<table>
<% for item in @cart.items %>
<tr>
<td><%= item.quantity %>×</td>
<td><%=h item.title %></td>
<td class="item-price"><%= en_euros(item.price) %></td>
</tr>
<% end %>
<tr class="total-line">
<td colspan="2">Total</td>
<td class="total-cell"><%= en_euros(@cart.total_price) %></td>
</tr>
</table>
<%= button_to "Empty cart", :action => :empty_cart %>
39. It3 D) Gestionar Errores
Echemos un vistazo a los últimos mapeos de routes.rb
map.connect ':controller/:action/:id'
Automáticamente lo que va después de la acción se mapea al
parámetro id.
http://localhost:3000/store/add_to_cart/fake
Couldn't find Product with ID=fake
def add_to_cart
@cart=find_cart
begin
@cart.add_product Product.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
logger.error("Se intentó acceder al producto con id=#{params
[:id]}")
flash[:notice]="Oooops, we could't process your request"
redirect_to :action=>:index
end
end
40. It4. Una pizca de AJAX
• En esta iteración aprenderemos a usar renderizado parcial de vistas, a actualizar
dinámicamente la página con AJAX, y a manipuilar el DOM.
• Tareas:
• A) Refactorizar el carrito
• B) Carrito basado en AJAX
• C) Resaltar los cambios
• D) Ocultar el carrito cuando está vacío
• E) Hacer que el carrito funcione cuando Javascript está deshabilitado
41. It5. Gestionar la compra
• En esta iteración aprenderemos a conectar tablas del modelo con claves
ajenas. Usar las relaciones belongs_to y has_many. A crear formularios basados
en modelos y a conectar dichos furmularios, con los modelos y las vistas.
• Tareas:
• A) Caputurar una orden de compra
42. It6. Gestionar los usuarios
• En esta iteración aprenderemos a añadir autenticación a una sesión de usuario,
a usar transacciones y crear un hook que añade nueva funcionalidad a
ActiveRecord
• Tareas:
• A) Añadir Usuarios
• B) Autenticación
• C) Controlar el acceso
43. Posibles prácticas
• Notable
• Crear una pequeña aplicación de gestión básica.
• Añadir funcionalidad de subscripción y envío de newsletters a la aplicación
existente (http://guides.rubyonrails.org/action_mailer_basics.html)
• Añadir cualquier funcionalidad no trivial a la aplicación mediante plugins:
(eg. paginación y búsqueda en vistas de listados)
• Sobresaliente
• Instalar los frameworks de pruebas BDD Rspec y Cucumber y diseñar e
implementar un caso de uso dirigido por la prueba.
• Crear un plugin de rails (http://guides.rubyonrails.org/plugins.html)
44. Referencias
• http://guides.rubyonrails.org/
• http://railsapi.com/doc/rails-v2.3.5/
• http://www.erikveen.dds.nl/distributingrubyapplications/rails.html
• Agile Web Development with Rails, Third Edition (The Pragmatic Bookshelf)
• The Rails Way (Addison-Wesley Professional)
• Deploying Rails Applications: A Step-By-Step Guide (The Pragmatic Bookshelf)
• Rails Cookbook (O`reilly)
• Behaviour driven development with RSpec, Cucumber and Friends (The Pragmatic
Bookshelf)