#224 Controllers in Rails 3
- Download:
- source codeProject Files in Zip (95.2 KB)
- mp4Full Size H.264 Video (19.7 MB)
- m4vSmaller H.264 Video (12.4 MB)
- webmFull Size VP8 Video (30.9 MB)
- ogvFull Size Theora Video (28.7 MB)
A pesar de que la mayor parte de los cambios en los controladores de Rails 3 han tenido lugar entre bambalinas, sí que hay algunas novedades en la interfaz con la que como desarrolladores en Rails vamos a tener que trabajar cada día. En este episodio repasaremos parte de esta nueva funcionalidad.
Filtrado de parámetros
El primer cambio que veremos ha ocurrido en ApplicationController
. En una aplicación Rails 2 podíamos filtrar algunos de los parámetros para evitar que aparezcan en claro en la traza de nuestra aplicación con el método filter_parameter_logging
, como se muestra aquí:
class ApplicationController < ActionController::Base protect_from_forgery filter_parameter_logging :password end
Este método ya no existe en Rails 3, por lo que deberíamos eliminarlo. El filtrado de parámetros ahora se configura en /config/application.rb
. Al final del archivo aparece el código que configura los parámetros que se deberían filtrar.
# Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password]
La línea anterior viene por defecto en cualquier aplicación nueva de Rails 3. Esto es interesante porque resulta bastante común olvidarse de añadir este filtrado. Por supuesto, si lo dejamos tal cual sólo filtrará los parámetros de claves y tendremos que ampliarla línea anterior si queremos filtrar otros parámetros tales como números de tarjetas de crédito.
Atajos en las redirecciones
A continuación veremos el código de ProductsController
en una aplicación de tienda que hemos escrito y cómo se controlan las redirecciones cuando se invoca la acción create
o update
en una aplicación Rails típica que tiene los siete métodos REST. Si miramos el código del método create
veremos que una vez que se ha guardado un Product
válido la acción establece un mensaje de flash
y luego redirige a la acción show
del producto recién creado.
def create @product = Product.new(params[:product]) if @product.save flash[:notice] = "Successfully created product." redirect_to @product else render :action => 'new' end end
Se trata de un comportamiento muy común en los controladores por lo que en Rails 3 podemos combinar las dos líneas de forma que el mensaje flash
se convierte en un parámetro del método redirect_to
.
def create @product = Product.new(params[:product]) if @product.save redirect_to @product, :notice => "Successfully created product." else render :action => 'new' end end
También podemos poner :notice
o :alert
(si queremos usar otro tipo de mensajetes tendremos que asignarlo como antes el hash de flash
). Esta funcionalidad también ha sido añadida a Rails 2.3.6 por lo que si usamos la última versión de Rails 2 también podremos utilizar este tipo de atajos.
Otro atajo nuevo consiste en que el método redirect_to
puede aceptar un objeto de modelo como parámetro. Por ejemplo, pasar una instancia de Product
(@product
en nuestro caso) es equivalente a utilizar product_path(@project)
y se traduce en la ruta de la acción show
de dicho producto. Si quisiésemos redirigir a la página de edición de dicho producto anteriormente usaríamos edit_product_path(@product)
, pero ahora podemos usar otro atajo, en lugar de escribir
redirect_to edit_product_path(@product)
podemos escribir:
redirect_to [:edit, @product]
Esto se traducirá a la misma ruta de edición de productos que antes, pero con menos código (esto funcionará también, al igual que la opción :notice
anteriormente descrita, en la última versión de Rails 2). Este atajo también funciona con recursos anidados por lo que si un producto pertenece a una categoría podemos hacer la redirección a:
redirect_to [@category, @product]
Este nuevo parámetro en forma de lista funciona en cualquier sitio donde podamos generar una URL y podemos usarlo tanto en los controladores como en nuestra vistas en el método link_to
.
Cookies permanentes
Lo siguiente que veremos son las cookies permanentes. Supongamos que cuando un usuario crea un nuevo producto queremos almacenar el id
de dicho producto en una cookie. Para eso utilizaríamos el método cookies
:
def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id redirect_to @product, :notice => "Successfully created product." else render :action => 'new' end end
Este código crea una cookie de sesión que perdurará sólo durante el tiempo que el usuario tenga su navegador abierto. Si queremos crear una cookie con una fecha explícita de expiración con Rails 2 tenemos que convertir el valor en un hash y establecer un tiempo de expiración así:
cookies[:last_product_id] = { :value => @product_id, :expires => 1.month.from_now }
En Rails 3 (o Rails 2.3.6 y superiores) podemos usar el nuevo método cookies.permanent
para crear una cookie con un tiempo de expiración lejano.
cookies.permanent[:last_product_id] = @product.id
También pueden crearse con esta técnica cookies firmadas (no entraremos aquí en detalle, hay una gran anotación en el blog de Pratik Naik cuya lectura recomendamos). Básicamente podemos usar el método cookies.permanent.signed
para generar una cookie cifrada y leerla más adelante. Nótese que para que esto funcione tenemos que configurar adecuadamente el valor de cookie_verifier_secret
en nuestra aplicación.
Uso de respond_with
Lo último que nos queda por repasar en este episodio es posiblemente la más importante novedad en los controladores de Rails 3: respond_with
. Vamos a demostrar esta funcionalidad añadiendo un servicio REST sobre una interfaz XML en nuestro controlador ProductsController
. Esto quiere decir que cada una de las acciones de dicho controlador podrá devolver una respuesta XML además de su respuesta HTML normal.
En una aplicación Rails 2 lo implementaríamos utilizando el método respond_to
en cada acción, así:
def index @products = Product.all respond_to do |format| format.html format.xml { render :xml => @products } end end
Dentro del bloque respond_to
especificaremos cada uno de los formatos a los que puede responder la acción, mostrando la versión XML del listado de productos si la petición solicita el formato XML, lo que se complica rápidamente si tenemos que hacerlo para todas y cada una de las acciones de un controlador.
En Rails 3 podemos reemplazar este código por una llamada a respond_with
pasándole el objeto adecuado, en este caso el array con productos. Para que respond_with
funcione también tenemos que usar el método de clase respond_to
para especificar los tipos MIME a los que el controlador debería responder de forma que respond_with
sepa qué hacer en cada acción.
class ProductsController < ApplicationController respond_to :html, :xml def index @products = Product.all respond_with @products end end # Other methods end
Una vez que hemos cambiado ProductsController
para que utilice respond_with
podremos ver si los cambios han surtido efecto. Si visitamos la página del listado de productos en un navegador veremos la acción index
devolviendo, como sería de esperar, HTML.
Y si en la URL ponemos .xml
obtendremos la respuesta en XML.
¿Cómo funciona el método respond_with
? Para una petición GET sencilla como index
busca un fichero de vista con el tipo MIME adecuado, por ejemplo .html.erb
, y lo mostrará. Si respond_with
no es capaz de encontrar esa plantilla intentará llamar al método to_xml
o to_json
para mostrar el objeto recibido en el formato apropiado.
Sabiendo esto podemos actualizar los métodos show
y new
en el controlador ProductsController
para que funcione con respond_with
.
def show @product = Product.find(params[:id]) respond_with @product end def new @product = Product.new respond_with @product end
La acción create
es un poco más compleja porque tiene algo de código específico de HTML, por ejemplo invoca redirect_to
y render
, cosa que no querremos hacer en una respuesta XML. Podemos resolver esto simplemente eliminándolos del método y añadiendo una llamada a respond_with
con lo que el método quedaría así:
def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id flash[:notice] = "Successfully created product." end respond_with(@product) end
Esto es posible porque respond_with
hará un redirección o un render
dependiendo de si el objeto que recibe es válido. Con esto podemos añadir respond_with
a los otros métodos del controlador elinando todo código que sea específico de HTML. Cuando hayamos acabado nuestro controlador tendrá el siguiente aspecto:
class ProductsController < ApplicationController respond_to :html, :xml def index @products = Product.all respond_with @products end def show @product = Product.find(params[:id]) respond_with @product end def new @product = Product.new respond_with @product end def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id flash[:notice] = "Successfully created product." end respond_with(@product) end def edit @product = Product.find(params[:id]) respond_with(@product) end def update @product = Product.find(params[:id]) if @product.update_attributes(params[:product]) flash[:notice] = "Successfully updated product." end respond_with(@product) end def destroy @product = Product.find(params[:id]) @product.destroy flash[:notice] = "Successfully destroyed product." respond_with(@product) end end
El método respond_with
puede ser un poco confuso, así que lo resumiremos aquí.
Para una petición GET respond_with
en primer lugar buscará una vista que responda a ese formato específico, y si la encuentra la mostrará. Si no, llamará a to_xml
(o cualquier formato que se haya solicitado) en el objeto que reciba.
Para cualquier otro tipo de petición respond_with
comprueba primero que el objeto recibido tiene errores. Si los tiene entonces se mostrará la vista apropiada (por ejemplo new
para la acción create
y edit
para update
). Si no hay errores entonces se redirigirá a la página de dicho objeto (es decir, a la accion show
).
Esto se puede probar editando uno de nuestros productos.
Cuando hacemos clic en el botón “Update Product” se invoca la acción update
y respond_to
ejecutará la redirección a la página de dicho producto.
De la misma forma, si hacemos clic en el enlace “destroy” acabaremos siendo redirigidos a la página de índice.
Si el comportamiento por defecto de respond_with
no se adapta a ciertas partes de nuestra aplicación se puede personalizar. Por ejemplo, supongamos que cuando actualizamos un producto queremos que la aplicación nos redirija a la acción index
en lugar de a la acción show
. En este caso, podemos pasar una opción a respond_with
de forma que redirija a una acción diferente, por ejemplo para redirigir a la página de índice podemos cambiar la acción update
así:
def update @product = Product.find(params[:id]) if @product.update_attributes(params[:product]) flash[:notice] = "Successfully updated product." end respond_with(@product, :location => products_url) end
También podemos puentear ciertos formatos pasando un bloque a respond_with
exactamente igual que haríamos con respond_to
. Así que si para el formato XML de la acción edit
tan sólo queremos mostrar texto podemos hacerlo:
def edit @product = Product.find(params[:id]) respond_with(@product) do |format| format.xml { render :text => "I'm XML!" } end end
Si visitamos la versión XML de la página de edición veremos que sale dicha cadena.
Por último si necesitamos personalizar por completo la vista podemos pasar nuestra propia clase respondedora, sobre la que el controlador delegará todo.
respond_with(@product, :responder => MyResponder)
Para inspirarnos a la hora de crear una clase respondedora podemos mirar el código fuente de la clase Responder de Rails 3. El código es bastante fácil de leer y está bien documentado, y por lo menos nos dará una idea de cómo funciona una clase respondedora.
Y con esto acabamos el episodio de hoy. Como hemos visto respond_with
es una manera muy cómoda de gestionar múltiples formatos pero si necesitamos un alto grado de personalización nos será más fácil crear nuestra propia clase respondedora.