03: REST, Recursos, y RAILS - LPC-Ltda/Ruby-on-Rails GitHub Wiki
Antes de que REST llegara, yo (y todo el resto del mundo) casi nunca sabía donde poner las cosas. -Jonas Nicklas on the Ruby on Rails mailing list.
He llegado a aceptar que ser fundamentalista cuando creo un nuevo controlador para mantenerme adherido a REST me ha servido cada vez mejor. Cada vez que me arrepiento del estado de mis controladores, ha sido porque he tenido muy pocos. He estado tratando de sobrecargar demasiado las cosas. -DHH interview on Full Stack Radio http://fullstckradio.com/32-
Representational state transfer (REST) es un tópico complejo en teoría de la información, y una exploración profunda de él va más allá del alcance de este capítulo. (Ver http://www.ics.uci.edu/~fielding/pubs/dissertations/top.htm, en particular capítulos 5 y 6, además http://rest.blueoxen.net/cgi-bin/wiki.pl). Nosotros tocaremos algunos conceptos clave.
La razón por la cual dedicamos un capítulo completo a REST es que uno de los problemas inherentes que todos los desarrolladores enfrentan es decidir como nombrar y organizar los recursos y acciones de sus aplicaciones. Y da la casualidad de que la gran mayoría de las aplicaciones de Rails están respaldadas por bases de datos y se adaptan bien al paradigma REST (también conocido como "diseño RESTful").
REST es descrito por su creador, Rot T. Fielding, como un estilo arquitectónico de redes, específicamente el estilo manifiesto en la arquitectura de la World Wide Web. En efecto, Fielding no es sólo el creador de REST sino también uno de los autores del protocolo HTTP mismo. REST y la web tienen una relación cercana.
Fielding describe REST como una serie de restricciones impuestas sobre la interacción de componentes de un sistema. Básicamente, usted comienza con la proposición de máquinas que pueden hablar con otras, y comienza a reglar ciertas prácticas en unos y otros imponiendo restricciones que incluyen (entre otras) las sigueintes:
- Uso de una arquitectura cliente-servidor
- Comunicaciones sin estados
- Señalización explicita de cahes de respuesta
- Uso de los metodos de requerimientos HTTP: GET, POST, PUT y DELETE
(VEremos que significa cada uno de estos puntos más adelante en este capítulo)
A los sistemas que adhieren a REST se les llama RESTful, mucha de la web misma usa comunicación que cumple con REST, pero notablemente, hay mucho espacio para violaciones de los principios REST. Las restricciones de REST deben ser construidas en un sistema a propósito por su diseñador.
Para sacar el máximo provecho de este capítulo, la cosa más importante que debe comprender es que REST está diseñado para ayudarlo a proveer servicios usando el idioma nativo y los constructos de HTTP. Usted encontrará, si las busca, muchas discusiones comparando REST con, por ejemplo, SOAP -la idea clave de los argumentos pro-REST es que HTTP ya nos habilita para proveer servicios, así que no es necesario un nivel semántico sobre él, sólo usar lo que HTTP ya te da.
Una de las ventajas de REST es que crece relativamente bien para sistemas grandes, como la web. Otra es que alienta (manda incluso) el uso de identificadores de larga vida estables (URIs). Las máquinas conversan las unas con las otras enviando requerimientos y respuestas etiquetadas con estos identificadores. Los mensajes consisten en representaciones (manifestaciones en texto, XML, formato gráfico, etc.), recursos (descripciones conceptuales de alto nivel de contenidos), o simplemente encabezados HTTP.
Idealmente al menos, cuando usted le pide a una máquina una representación JSON de un recurso -digamos Romeo y Julieta- usted usará el mismo identificador y el mismo metadata de requerimiento indicando que usted necesita JSON, y usted tendrá la misma respuesta. Y si no es la misma respuesta, hay una razón -como, el recurso que usted esta recuperando es cambiable ("La actual transcripción para Estudiante #3994" por ejemplo).
El estilo REST caracteriza la comunicación entre componentes de sistemas (donde un componente es, un web browser o un servidor) como una serie de requests para los cuales las respuestas son representaciones de recursos.
Un recurso, en este contexto, es un "mapeo conceptual" (Fielding). Los recursos mismo no están atados a una base de datos, un modelo, o un controlador. Ejemplos de recursos incluyen los siguientes:
- La hora del día actual
- El historial de prestamos de un libro de la biblioteca
- El texto completo de El Principito
- Un mapa de la playa Jacksonville, Florida
- El inventario de un almacén
Un recurso puede ser singular o plural, cambiante (como la hora del día) o fijo (como el texto del El Principito). Es básicamente una descripción de alto nivel de la cosa que usted está tratando de obtener cuando envía un requerimiento.
Un recurso puede ser también efímero, como el resultado de una autenticación en un servidor, o un filtrado temporal de una data tabulada.
Si es permanente, sujeta a cambios o efímera, el punto es que los recursos son sustantivos.
A continuación, es importante darse cuenta que lo que usted obtiene desde un servidor RESTful nunca es el recurso mismo sino una representación de él. Aquí es donde se despliega REST en los innumerables tipos de contenido y entregables reales que son el material de la web.. Un recurso puede, en un punto dado, estar disponible en un número de representaciones (incluso cero). Entonces su sitio puede ofrecer una versión en texto de El Principito pero también una versión en audio. Estas dos versiones pueden ser entendidas como el mismo recurso y pueden ser recuperadas vía el mismo identificador (URI). La diferencia en el tipo de contenido -una representación versus la otra- puede ser negociada separadamente en el requerimiento.
El soporte de REST en Rails consiste en métodos para definir recursos en el sistema de ruteo, diseñado para imponer un estilo, orden y lógica particular en sus controladores y, consecuentemente, en la forma en que el mundo ve su aplicación. Hay más que solo un conjunto de convenciones de nombres. (que también la hay). En la visión más amplia de las cosas, los beneficios que le trae usar el soporte REST de Rails caen en dos categorías:
- Conveniencia y buenas prácticas automáticas para usted
- Una completa interface REST de los servicios de su aplicación para todos los demás
Usted puede cosechar el primer beneficio incluso si usted no está preocupado por el segundo. De hecho, ese va a ser nuestro foco aquí: lo que el soporte REST en Rails puede hacer por usted en el sentido de hacer más agradable su código y su vida como desarrollador Rails más simple.
Yo no pretendo minimizar la importancia de REST mismo ni la seriedad del esfuerzo de proveer servicios basados en REST. Más bien, es un expediente; no podemos hablar de todo, y esta sección del libro trata principalmente sobre el enrutamiento y cómo hacerlo, por lo que privilegiaremos ver REST en Rails desde esa perspectiva.
Volviendo a temas prácticos, el foco del resto de este capítulo será mostrar cómo REST soporta el trabajo en Rails, abriendo la puerta a un estudio futuro y práctica, incluyendo el estudio de las disertaciones de Fielding y los principios teóricos de REST. No cubriremos todo aquí, pero lo que cubriremos será compatible con un tópico más amplio.
Y todo comienza con CRUD...
El acrónimo CRUD (create, read, update y delete) es el resumen clásico del espectro de operaciones en una base de datos. Es también una especie de grito de guerra para los practicantes de Rails. Porque nosotros nos dirigimos a nuestras bases de datos a través de abstracciones, estamos propensos a olvidar lo fácil que es. Esto se manifiesta principalmente en nombres excesivamente creativos para las acciones del controlador.
Si usted es ignorante del diseño RESTful, entonces habrá una fuerte tentación de llamar sus acciones add_item
y replace_email_address
y cosas como esa. Pero no las necesitamos y usualmente no debiéramos hacerlo. Cierto, el controlador no mapea a la base de datos en la forma en que el modelo lo hace. Pero las cosas se simplifican cuando asigna un nombre a sus acciones después de las operaciones de CRUD, o tan cerca de los nombres de esas operaciones como puede obtener.
Su sistema de ruteo no lo forzará a implementar la funcionalidad CRUD de su aplicación en una forma consistente, cualquiera sea el nombre de la acción. Elegir nombres CRUD es un tema de disciplina. Excepto... cuando usted usa las facilidades REST ofrecidas por Rails, esto pasa automáticamente.
Otra forma de verlo es que REST en Rails impica la estandarización de nombres de acciones. De echo, el corazón del soporte de REST de Rails es una técnica para crear manojos de rutas nombradas automáticamente -rutas nombradas que que son puestas juntas para apuntar a un conjunto de acciones específicas, predeterminadas.
Aquí la lógica es: es bueno dar nombres basados en CRUD a sus acciones. Es conveniente y elegante usar rutas nombradas. El soporte REST en Rails le da rutas nombradas que apunta a nombres de acciones basados en CRUD. Luego, usar las facilidades REST le permite acceder rápido a algunas buenas prácticas.
Atajo apenas describe cuán poco trabajo tienes que hacer para obtener una gran recompensa. Si pones.
resources :auctions
dentro de su archivo `config/routes.rb, usted habrá creado cuatro rutas nombradas, la cuales, en una forma que se describirá en esta capítulo, conectan con siete acciones del controlador. Y esas acciones tienen nombres tipo CRUD como usted podrá ver.
Como la mayor parte de Rails, el soporte para aplicaciones RESTful es "dogmático" (opinionated); esto es, ofrece una forma particular de diseñar una interface REST, y cuanto más usted juegue en ella, mayor comodidad sentirá de ella. La mayoría de las aplicaciones Rails están respaldadas por una base de datos, y los Rails que adoptan REST tienden a asociar un recurso muy estrechamente con un modelo de Active Record o con una pila modelo/controlador.
De hecho, usted escuchará a personas usar la terminología vagamente. Por ejemplo, ellos dirán que han creado un recurso Look (book resource). Lo que quieren decir, en la mayoría de los casos es que ellos han creado un modelo Book
, un controlador para book con un conjunto de acciones CRUD, y algunas rutas nombradas pertinentes al controlador (cortesía de resources :books
). Usted puede tener un modelo y controlador Book
, pero que es lo que realmente presenta al mundo como su recurso, en el sentido REST, existe un alto nivel de abstracción: El Principito, historia de prestamos, etc.
La mejor forma de obtener un manejo del soporte REST de Rails es ir desde lo conocido a lo desconocido. En este caso, desde el tópico de rutas nombradas al tópico más especializado de REST.
Cuando recién vimos las rutas nombradas, vimos ejemplos donde consolidabamos cosas dentro de un nombre de ruta. Al crear una ruta como
get 'auctions/:id' => "auctions#show", as: 'auction'
usted gana la habilidad de usar helpers amigables en situaciones como
link_to item.description, auction_path(item.auction)
La ruta se asegura de generar una ruta que gatillará la acción show
del controlador auctions
. Lo atractivo de este tipo de rutas nombradas es que es conciso y legible.
Ahora piense en términos de CRUD, la ruta nombrada auction_path
esta bien para una acción show
(la R de CRUD). Que pasa si buscamos nombres adecuados para rutas nombradas de las acciones create
, update
, y delete
?
Bien, usamos la ruta nombrada auctions_path
para la acción show
. Podemos construir nombres como auction_delete_path
y auction_create_path
, pero estos son incómodos. Realmente lo que queremos es hacer una llamada a auction_path
y que signifique diferentes cosas, dependiendo de a cual acción queremos apunte la URL.
Podemos diferenciar entre el singular (auction_path
) y el plural (auctions_path
). Un URL singular hace sentido, semánticamente, cuando usted está haciendo algo con un único objeto auction existente. Si usted está haciendo algo con auctions en general, el plural hace más sentido.
El tipo de cosas que usted hace con auctions en general considera la creación. La acción create
ocurre normalmente en un formulario:
form_tag auctions_path
Es plural porque no estamos diciendo "realice una acción sobre un auction particular" sino más bien "respecto a la colección de auctions, realice la acción creación" Si, estamos creando un auction, no muchos. Pero en el momento en que hacemos la llamada a nuestra ruta nombrada, auctions_path
, estamos direccionandonos a auctions en general.
Otro caso donde usted puede desear una ruta nombrada en plural es cuando usted desea un resumen de los objetos de un determinado tipo, o al menos un tipo de vista general, más que desplegar un objeto particular, este tipo de acciones es manejada usualmente con una acción index
. La acción index
típicamente carga un montón de data dentro de una o más variables, y la correspondiente vista la despliega como una lista o tabla (posiblemente más de una).
Aquí, una ves más nos gustaría estar habilitados para decir
link_to "Click here to view all auctions", auctions_path
Ya, aunque la estrategia de dividir auction_path
en un singular y un plural ha golpeado el muro: Tenemos dos lugares donde deseamos usar las rutas nombradas en plural. Uno es create; el otro es index, Pero ambos se verán así
/auctions
Como el sistema de ruteo sabrá cuando usamos auctions_path
como un link y cuando como un formulario, es decir una acción create
y no una index
? Necesitamos otro calificador, otra bandera, otra variable sobre la cual diferenciar.
El envío de formularios es POST por defecto. Las acciones index son GET. Esto significa que necesitamos que el sistema de ruteo se dé cuenta de esto.
/auctions submitted in a GET request!
versus
/auctions submitted in a POST request!
Son dos cosas diferentes. Tenemos que hacer que el sistema de ruteo genere la misma URL -/auctions
- pero con diferente método de request HTTP, dependiendo de las circunstancias.
Esto es lo que las facilidades REST del ruteo de Rails hacen por usted. Le permiten estipular que usted desea que /auctions
rutee diferentemente, dependiendo del método de request HTTP. Esto le permite definir rutas nombradas con el mismo nombre pero con inteligencia acerca de los verbos HTTP. En resumen, usa los verbos HTTP para proveer ese manejo extra necesario para lograr todo lo que usted desea lograr en una forma concisa.
La forma de hacer esto es usar el método de ruteo especial: resources
. Así luce para auctions:
resources :auctions
Esto es. Haciendo esta llamada dentro del archivo routes.rb
es equivalente a definir cuatro rutas nombradas. Y si usted mezcla y hace coincidir esas cuatro rutas nombradas con una variedad de métodos de requerimientos HTTP, terminará con siete útiles - muy útiles- permutaciones.
Llamar resources :auctions
envuelve sellar una serie de tratos con el sistema de ruteo. El sistema le entrega cuatro rutas nombradas. Entre ellas, estas cuatro rutas apuntan a siete acciones de controlador, dependiendo del método de requerimiento HTTP. A cambio, usted accede a usar nombres muy específicos para las acciones de su controlador: index
, create
, show
, update
, destroy
, new
y edit
.
Y no es un mal negocio, ya que mucho trabajo es hecho por usted y los nombres que debe usar son del tipo CRUD.
La Tabla 3.1 resume lo que pasa. Es un tipo de "tabla de multiplicar" que muestra lo que usted obtiene cuando cruza una ruta nombrada REST dada con un método de requerimiento HTTP. Cada caja (las no vacías) le muestran, primero, la URL que la ruta genera y, segundo, la acción que fue llamada cuando se reconoció la ruta. (La tabla lista métodos _path
y no _url
, pero usted obtiene ambos)
Tabla 3.1 Tabla de rutas REST que muestra Helpers, Paths y la acción del controlador resultante.
Método Helper | GET | POST | PATCH | DELETE |
---|---|---|---|---|
client_path (client) | /clients/1 show | /clients/1 update | /clients/1 destroy | |
clients_path | /clients index | /clients create | ||
edit_client_path (client) | /clients/1/edit edit | |||
new_client_path | /clients/new new |
(Las acciones edit
y new
tienen rutas nombradas únicas, y sus URLs tienen una sintaxis especial.)
Ya que que las rutas nombradas están ahora cruzadas con los métodos de request HTTP, usted necesitará conocer cómo especificar el método de request cuando genere una URL, para que sus clients_url
de GET y sus clients_url
de POST no gatillen la misma acción del controlador. La mayoría de de lo que usted puede hacer en este respecto puede ser resumido en unas pocas reglas:
- El método de requerimiento por defecto es GET.
- En una llamada
form_tag
oform_for
, el método POST será usado automáticamente. - Cuando lo necesite (lo cual en su mayoría será con las operaciones PATCH y DELETE), usted puede especificar un método de request junto a la URL generada por la ruta nombrada.
Un ejemplo de la necesidad de especificar una operación DELETE es una situación donde usted desea gatillar una acción destroy
con un link:
link_to "Delete", auction_path(auction), method: :delete
Dependiendo del método helper que usted este usando (como en el caso de form_for
), usted tendrá que poner el método dentro de un hash anidado:
form_for "auction", url: action_path(auction), html: { method: :patch } do |f|
El último ejemplo, el cual combina la ruta nombrada singular con el método PATCH, resultará en una llamada a la acción update
cuando se envíe el formulario (según la fila dos, columna cuatro de la tabla 3.1). Usted no tiene que programar normalmente esta funcionalidad específicamente, porque como veremos después en este libro, Rails automáticamente descifra si usted necesita un POST o PATCH si usted pasa un objeto al helpers del formulario.
Si usted viene de las versiones previas de Rails, usted estará asombrado de que la acción update
de una ruta RESTful este mapeada al verbo HTTP PATCH en lugar de PUT. En el documento de estándar HTTP RFC 5789, se dice que un requerimiento PUT a un recurso dado se entiende como su reemplazo completo sobre el servidor de origen. Sin embargo, cuando actualizamos un recurso en Rails, rara vez, si es que alguna, usted reemplaza un recurso completo cuando realiza una update
. Por ejemplo, cuando actualizamos un modelo Active Record, Rails setea el atributo timestamp updated_at
, no el cliente requerido.
De esta manera, para implementar mejor la semántica HTTP, Rails usa el verbo HTTP PATCH para actualizaciones. PATCH permite actualizciones totales y parciales de un recurso y es más apropiada a cómo Rails actualiza recursos.
Si usted está actualizando una aplicación Rails existente, el verbo HTTP PUT aún mapeará a la acción update
en las rutas REST, pero recomendamos cambiarla por PATCH.
Como habrá notado, algunas rutas REST son singulares y otras plurales, la lógica es la siguiente:
- Las rutas para
show
,new
,update
, ydestroy
son singulares porque ellas trabajan sobre un recurso particular. - El resto de las rutas son plural. Ellas trabajan con colecciones de recursos relacionados.
Las rutas REST singulares requieren un argumento porque ellas necesitan poder descifrar el id del miembro de la colección referenciado.
item_url(item) # show, update, or destroy, depending on HTTP verb
Usted no tiene que llamar el método id
sobre item
. Rails lo descifrará (llamando to_param
sobre el objeto que se pasó).
Como muestra la Tabla 3.1, new
y edit
obedecen cierto tipo especial de convención de nombres REST. La razón de esto tiene que ver con create
y update
y en cómo se relacionan con ellos.
Típicamente, las operaciones create
y update
involucran en envío de un formulario. Esto significa que ellas involucran realmente dos acciones, dos requerimientos, cada una:
- La acción que resulta en el despliegue del formulario
- La acción que procesa el input del formulario cuando el formulario es enviado.
La forma en que esto opera con el ruteo REST es que la acción create
esta íntimamente relacionado con la acción preliminar new
, y update
relacionado con edit
. Estas dos acciones, new
y edit
, son realmente acciones asistentes: Todo lo que ellas supuestamente hacen es mostrar al usuario un formulario como parte del proceso de creación o actualización de un recurso.
Calzar estos escenarios en dos partes dentro del panorama de recursos es un poco complicado. Un formulario para editar un recurso no es en si mismo, realmente un recurso. Es más como un pre-recurso. Un formulario para crear un nuevo recurso es un tipo de recurso si usted asume que siendo nuevo - es decir, inexistente- es algo que un recurso puede ser y aún ser recurso!
Esta línea de razonamiento es demasiado filosófica para ser útil. La línea final, como ha sido implementada en Rails RESTful, es la siguiente: La acción new
es entendida como algo que le proporciona un nuevo y único (no plural) recurso. Sin embargo, ya que el verbo lógico para esta transacción es GET, y hacer un GET de un único recurso es hablar también de la acción show
, new
necesita una ruta nombrada con su nombre.
Esta es la razón por la que tenemos que usar
link_to "Create a new item", new_item_path
para hacer un link a la acción items/new
.
La acción edit
es entendida como algo que no le brinda una representación completa de un recurso, exactamente, sino un tipo de edición del recurso show. Así que este usa el mismo URL que show
pero con un tipo de modificador, en la forma de /edit
, colgando del final, lo cual es consistente con la forma de URL para new
:
/items/5/edit
La ruta nombrada correspondiente es edit_item_url(@item)
. Como con new
, la ruta nombrada para edit
cuenta con un poco más de información para diferenciarla de la ruta REST existente empleada para show
que hace un GET de un recurso único.
Recién vimos cómo Rails rutea los request PATCH y DELETE. Algunos clientes HTTP pueden usar estos verbos, pero los formularios en un browser de web no pueden ser enviados con algo que no sea un POST. Rails provee un hack que no hace otra cosa que estar pendiente de cuando eso sucede.
Un requerimiento PATCH o DELETE originados en un browser, en el contexto del REST en Rails, es realmente un requerimiento POST con un campo oculto llamado method
seteado a "patch"
o "delete"
. La aplicación Rails procesa el requerimiento lo tomará y ruteará el requerimiento apropiadamente a la acción update
o destroy
.
Usted puede decir, entonces, que el soporte de REST en Rails está más allá de su tiempo. Los componentes REST usando HTTP deben entender todos los métodos de requerimientos. Ellos no lo hacen, por ello Rails fuerza el tema. Como desarrollador tratando de comprender cómo las rutas nombradas mapean a nombres de acciones, no necesita preocuparse acerca de esta pequeña trampa. Y esperamos que no sea necesario algún día.
Es posible agregar las opciones :except
y :only
al llamado de resources
con el propósito de limitar las rutas generadas.
resources :clients, except: [:index]
resources :clients, only: [:new, :create]
Si da el salto de pensar en los recursos como cosas estrictamente almacenadas en una base de datos a conceptos en mi aplicación, entonces comienza a tener sentido por qué la limitación comienza a ser útil. Hay conceptos que solo requieren un par de rutas. Un ejemplo común es la autenticación: cuando inicia sesión en la aplicación, está creando una sesión, y cuando la cancela, la está destruyendo. Eso puede ser (y es) modelado de manera RESTful por bibliotecas como Devise.
Adicionalmente a resources
, hay una forma singular (o singleton) de ruteo de recursos: resource
. Ésta es usada para representar un recurso que sólo existe una vez en su contexto dado.
Una ruta de recursos singleton en el nivel superior de sus rutas puede ser apropiada cuando sólo hay un recurso de este tipo para toda la aplicación -quizá algo como un perfil por usuario.
resource :profile
Usted consigue casi todo el complemento de rutas de recursos - todos excepto la ruta de colecciónes (index
). Note que el nombre del método resource
, el argumento para el método, y todas las rutas generadas están en singular.
$ rake routes
profile POST /profile(.:format) profiles#create
new_profile GET /profile/new(.:format) profiles#new
edit_profile GET /profile/edit(.:format) profiles#edit
GET /profile(.:format) profiles#show
PATCH /profile(.:format) profiles#update
PUT /profile(.:format) profiles#update
DELETE /profile(.:format) profiles#destroy
Esto asume que usted está en el contexto donde tiene sentido hablar de el perfil -el único- porque hay un usuario al cual el perfil pertenece. El alcance no es automático; usted tiene que autenticar el usuario y recuperar el perfil desde (y/o guardarlo) la base de datos explícitamente. No hay magia real aquí ni lectura de mentes, sólo una técnica de ruteo adicional a su disposición si la necesita.
Digamos que usted desea realizar operaciones sobre las ofertas: create
, edit
, etc. Usted sabe que cada oferta esta asociada con una subasta particular. Esto significa que donde quiera que usted haga algo a una oferta, usted estará realmente haciendo algo a una subasta/oferta -o, visto de otra forma, un nido subasta/oferta. Las ofertas están en la base de una estructura jerárquica descendente que siempre pasa a través de una subasta.
A lo usted apunta para esto es a una URL que luzca así
/auctions/3/bids/5
Lo que hace dependerá de el verbo HTTP que lo acompañe, por supuesto. Pero la semántica de la URL es el recurso que puede ser identificado como el bid (oferta) 5, que pertenece a la auction (subasta) 3.
Por que no sólo bids/5
y nos saltamos la auction
? Por un par de razones. Primero, la URL es más informativa -más larga, es verdad, pero larga en el sentido de contarle algo acerca del recurso. Segundo, gracias a la forma en que las rutas REST fueron implementadas en Rails, este tipo de URL le da acceso inmediato al id
de auction
via params[:auction_id]
.
Para crear rutas de recursos anidadas, ponga esto en su routes.rb
:
resources :auctions do
resources :bids
end
Lo que esto le cuenta al mapeador de rutas es que usted desea rutas REST para los recursos auction
-esto es, usted desea auctions_url
, edit_auction_url
, y todo el resto de ellas. Usted también necesita rutas REST para bids
: auction_bids_url
, new_auction_bid_url
, etc.
Sin embargo, el comando de recurso anidado lo lleva a hacer una promesa. Usted está prometiendo que donde quiera que use una ruta nombrada de una oferta, usted proveerá un recurso auction
en el cual pueda ser anidada. En el código de su aplicación, esto se traduce en un argumento en el método de ruta nombrado:
link_to "See all bids", auction_bids_path(auction)
Cuando usted hace esta llamada, usted habilita al sistema de ruteo a agregar la parte /auctions/3
antes de la parte /birds
. Y en el extremo receptor -en este caso, en la acción bids/index
, la cual es donde la URL apunta -usted encontrará el id
de auction
en params[:auction_id]
. (Esta es una ruta REST plural, usando GET. Ver tabla 3.1)
Usted puede anidar con cualquier profundidad. Cada nivel de anidado agrega uno al número de argumentos que debe entregar a las rutas anidadas. Esto significa que para una ruta singular (show
, edit
, destroy
), usted necesita al menos dos argumentos:
link_to "Delete this bid", auction_bid_path(auction, bid), method: :delete
Esto habilitará al sistema de ruteo para obtener la información que necesita (escencialmente auction.id
y bid.id
) con el fin de generar la ruta.
Alternativamente, en lugar de especificar la ruta a ser usada en un helper de vista, como link_to
, usted puede simplemente pasar el objeto.
link_to "Delete this bid", [auction, bid], method: :delete
Debido a que el objeto en este ejemplo es un Array
. Rails infierre que la ruta está anidada. Y basado en el orden y nombres de las clases de los objetos en el Array
. Rails usara el path auction_bid_path
detrás de la escena.
Algo que no hemos discutido aún es cómo las rutas RESTful son mapeadas a un controlador dado. Esto fue sólo presentado como algo que pasaba automáticamente, lo cual de hecho ocurre, basado en el nombre del recurso.
Volviendo atrás a nuestro ejemplo, dada una ruta anidada
resources :auctions do
resources :bids
end
Hay dos controladores que entran en juego: el AuctionsController
y el BidsController
.
Vale la pena anidar? Para rutas únicas, una ruta anidada usualmente no le cuenta nada que usted no pudiera averiguar de otra forma. Después de todo una oferta pertenece a una subasta.
Esto significa que usted puede acceder a bid.auction_id
tan fácil como a params[:id]
, asumiendo que usted ya tiene un objeto bid.
Además, el objeto bid no depende del anidado. Usted tendrá params[:id]
seteado en 5, y usted puede extraer ese registro directamente de la base de datos. Usted no necesita saber a que subasta pertenece.
Bid.find(params[:id])
Una razón común para el uso juicioso de recursos anidados, y la más frecuentemente expresada por David, es la facilidad con la que usted puede hacer cumplir permisos y restricciones basadas en el contexto. Típicamente, un recurso anidado sólo puede ser accesado en el contexto de su recurso padre, y es realmente fácil hacer cumplir que en su código basado en la forma en que usted carga el recurso anidado usando la asociación Active Record del padre.
auction = Auction.find(params[:auction_id])
bid = auction.bids.find(params[:id]) # prevents auction/bid mismatch
Si usted necesita agregar una oferta a una subasta, la URL de su recurso anidado sería
http://localhost:3000/auctions/5/bids/new
La subasta es identificada en la URL más que tener que desordenar la data de su formulario de nueva oferta con campos ocultos o reeditando prácticas no-REST.
Jamis Buck es una figura muy influyente en la comunidad Rails, casi tanto como el mismo David. En febrero del 2007, a través de su blog (http://weblog.jamisbuck.org/2007/2/5/nesting-resources), básicamente nos contó que el anidado profundo era una mala cosa y propuso la siguiente regla de oro: Los recursos nunca deben ser anidados a más de un nivel de profundidad.
Este consejo esta basado en la experiencia y preocupaciones acerca de la practicidad. Los métodos helpers de rutas anidadas a más de dos niveles de profundidad se hacen largos y pesados. Es fácil cometer errores con ellos y es difícil averiguar que está mal cuando no trabajan como esperábamos.
Asumamos que en nuestra aplicación, las ofertas tienen múltiples comentarios. Podríamos anidar comentarios bajo las ofertas en un ruteo como este:
resources :auctions do
resources :bids do
recources :comments
end
end
A cambio, Jamis quiere que hagamos lo siguiente:
resources :auctions do
resources :bids
end
resources :bids do
recources :comments
end
resources :comments
Note que cada recurso (excepto auctions) es definido dos veces: una en la cima y otra en su contexto. La razón? Cuando se trata del alcance padre-hijo, usted necesito sólo dos niveles para trabajar con él. Las URLs resultantes son más cortas y es más fácil trabajar con los métodos helpers.
auctions_path # /auctions
auctions_path(1) # /auctions/1
auction_bids_path(1) # /auctions/1/bids
bid_path(2) # /bids/2
bid_comments_path(3) # /bids/3/comments
comment_path(4) # /comments/4
Personalmente no sigo las indicaciones de Jamis todo el tiempo en mis proyectos. Pero he notado algo acerca de limitar la profundidad de sus recursos anidados: ayudan a la mantención en el largo plazo.
Muchos de nosotros no estamos de acuerdo con el venerable Jamis. Quieres entrar a puñetazos a una conferencia Rails? Pregunta a la gente si creen que los recursos deben estar anidados a más de un nivel de profundidad.
Como en Rails 2.3, las rutas de recursos aceptan una opción :shallow
que ayuda a acortar las URLs donde es posible. El objetivo es dejar fuera los segmentos de URL de las colecciones padres donde no se necesiten. El resultado final es que las únicas rutas anidadas generadas son para las acciones :index
, :create
y :new
. El resto son puestas en sus propios contextos de URL superficiales.
Es más fácil ilustrar que explicar, así que definamos un conjunto anidado de recursos y setiemos :shallow
a true
:
resources :auctions, shallow: true do
resources :bids do
resources :comments
end
end
Esto es codificado alternativamente de esta forma:
resources :auctions do
shallow do
resources :bids do
resources :comments
end
end
end
Las rutas resultantes son las siguientes:
bid_comments GET /bids/:bid_id/comments(.:format)
POST /bids/:bid_id/comments(.:format)
new_bid_comment GET /bids/:bid_id/comments/new(.:format)
edit_comment GET /comments/:id/edit(.:format)
comment GET /comments/:id(.:format)
PATCH /comments/:id(.:format)
PUT /comments/:id(.:format)
DELETE /comments/:id(.:format)
auction_bids GET /auctions/:auction_id/bids(.:format)
POST /auctions/:auction_id/bids(.:format)
new_auction_bid GET /auctions/:auction_id/bids/new(.:format)
edit_bid GET /bids/:id/edit(.:format)
bid GET /bids/:id(.:format)
PATCH /bids/:id(.:format)
PUT /bids/:id(.:format)
DELETE /bids/:id(.:format)
auctions GET /auctions(.:format)
POST /auctions(.:format)
new_auction GET /auctions/new(.:format)
edit_auction GET /auctions/:id/edit(.:format)
auction GET /auctions/:id(.:format)
PATCH /auctions/:id(.:format)
PUT /auctions/:id(.:format)
DELETE /auctions/:id(.:format)
Si usted analiza las rutas generadas cuidadosamente, notará que las partes anidadas de la URL son incluidas sólo cuando son necesarias para determinar que data desplegar.
Uno de los principios fundamentales que los desarrolladores de Rails siguen es "no se repita a si mismo" (DRY). A pesar de que este es el caso, el archivo config/routes.rb
puede ser propenso a tener repeticiones en la forma de rutas anidadas que son compartidas a través de múltiples recursos. Por ejemplo, asuma que en nuestro ejemplo recurrente que tanto las subastas como las ofertas pueden tener comentarios asociados.
resources :auctions do
resources :bids
resources :comments
resources :image_attachments, only: :index
end
resources :bids do
resources :comments
end
Para eliminar alguna duplicación de código y para encapsular comportamientos compartidos a través de las rutas, Rails introduce el método de ruteo concern
.
concern :commentable do
resources :comments
end
concern :image_attachable do
resources :image_attachments, only: :index
end
Para agregar un concern de enrutamiento a una ruta RESTful, hay que pasar el concern a la opción :concerns
.
resources :auctions, concerns: [:commentable, :image] do
resources :bids
end
resources :bids, concerns: :commentable
La opcion :concerns
puede aceptar uno o más concerns de ruteo.
Las rutas RESTful de Rails le proporcionan un buen paquete de rutas nombradas, mapeado a las útiles acciones comunes del controlador -el superconjunto CRUD que usted ya conoce- Algunas veces, sin embargo, usted desea personalizar cosas un poco más mientras aún toma ventaja de la convención de nombres de rutas REST y la aproximación tipo tabla de multiplicar para mezclar rutas nombradas y métodos HTTP de requerimientos.
Las técnicas para hacer esto son útiles cuando, por ejemplo, usted tiene más de una forma de ver un recurso que puede ser descrita como showing
. Usted no puede (o no debiera poder) usar la acción show
misma para más de una de tales vistas. A cambio, usted necesita pensar en términos de diferentes perspectivas sobre un recurso y crear URLs para cada una.
Por ejemplo, digamos que deseamos hacer posible retractarse de una oferta. La ruta anidada básica para las ofertas lucirá así.
resources :auctions do
resources :bids
end
Nos gustaría tener una acción retract
que muestre un formulario (y quizás haga un análisis de retractibilidad). retract
no es lo mismo destroy
; es más como un portal hacia destroy
. Es similar a edit
, que sirve como un portal de formulario para update
. Siguiendo el paralelo con edición / actualización, queremos una URL, que se parece a:
Nos gustaría tener una acción retract
que muestre un formulario (y tal vez hacer algo de screening para la retractabilidad). Hacer un retract
no es lo mismo que un destroy
; es más como un portal a destroy
. Es similar a edit
, el cual sirve como portal para update
. Siguiendo el paralelo con edit
/update
, deseamos una URL que luzca así
/auctions/3/bids/5/retract
y un método helper llamado retract_auction_bid_url
. La forma en que usted logra esto es especificando una ruta de member
extra para bids
, como en el Listado 3.1.
Listado 3.1 Agregando la ruta de un miembro extra
resources :auctions do
resources :bids do
member do
get :retract
end
end
end
Entonces usted puede agregar un link de retractación en su vista usando
link_to "Retract", retract_bid_path(auction, bid)
y la URL generada incluirá el modifcador /retract
. Dicho esto, usted probablemente hará que este link traiga un formulario de retractación (y no gatillar el proceso de retractación mismo), La razón por la que digo esto es porque, de acuerdo a los principios de HTTP, el requerimiento GET no debería modificar el estado del servidor; para eso está el requerimiento POST.
Así que cómo gatillará usted una retractacion actual? Basta con agregar una opción :method
al link_to
?
link_to "Retract", retract_bid_path(auction, bid), method: :post
No exactamente. Recuerde que en el listado 3.1 definimos la ruta retract como una get
, así que POST no será reconocido por el sistema de ruteo. La solución es definir un miembro extra con post
, como este:
resources :auctions do
resources :bids do
member do
get :retract
post :retract
end
end
end
Si usted está manejando más de un verbo HTTP con una única acción, usted cambiar usando una declaración match
única y una opción :via
, como esta:
resources :auctions do
resources :bids do
member do
match :retract, via: [:get, :post]
end
end
end
Gracias a la flexibilidad del sistema de ruteo, podemos apretar incluso más usando match
con una opción :on
como
resources :auctions do
resources :bids do
match :retract, via [:get, :post], on: :member
end
end
lo cual resultará en una ruta como esta (salida de rake routes
):
retract_auction_bid GET|POST /auctions/:auction_id/bids/:id/retract(.:format) bids#retract
Usted puede usar la misma técnica de ruteo para agregar rutas que conceptualmente apliquen a una colección completa de recursos:
resources :auctions do
collection do
match :terminate, via: [:get, :post]
end
end
Aquí en su versión reducida:
resources :auctions do
match :terminate, via [:get, :post], on: :colection
end
Este ejemplo le dará un método terminate_auctions_path
, el cual produce un mapeo de URL a la acción terminate
del controlador auctions. (Un ejemplo un poco bizarro, tal vez, pero la idea es que pueda habilitarlo a terminar todas las auctions de una vez).
Entonces usted puede afinar el comportamiento del ruteo -incluso el comportamiento de ruteo RESTful- de su aplicación, así usted puede arreglar para los casos especiales y especializados mientras aún piensa en términos de recursos.
Ocasionalmente, usted puede desear desviarse de la convención de nombrado por defecto para las rutas REST de Rails, La opción :path_names
le permite especificar mapeos alternativos de nombres. El código ejemplo muestra cambios de las acciones new
y edit
a sus equivalentes en español.
resources :projects, path_names: { new: 'nuevo', edit: 'cambiar' }
Las URLs cambian (pero los nombres de los métodos helpers generados no).
GET /projects/nuevo(.:format) projects#new
GET /projects/:id/cambiar(.:format) projects#edit
Usted puede usar la opción :controller
para mapear un recurso a un controlador diferente del que se usaría por defecto. Esta característica es ocasionalmente útil para hacer un alias a un nombre más natural.
resources :photos, controller: "images"
El sistema de ruteo tiene una sintaxis ordenada para especificar rutas que sólo aplican a recursos nuevos -los que todavía no han sido guardados-. Usted declara rutas extras dentro de un block anidado new
como este:
resources :reports do
new do
post :preview
end
end
Esta declaración resultará en la definición de la siguiente ruta:
preview_new_report POST /reports/new/preview(.:format) reports#preview
Se refiere a su nueva ruta dentro de una vista formulario modificando la :url
predeterminada
form_for(report, url: preview_new_report_path) do |f|
...
= f.submit "Preview"
Refiriendose a acciones member y collection adicionales, David ha sido citado diciendo, "Si usted está escribiendo demasiados métodos adicionales que la repetición está empezando a aproblemarlo, usted debe revisar sus intenciones. Usted no está siendo tan RESTful como podría ser".
La última frase es clave. Agregar acciones extra corrompe la elegancia de su diseño de aplicaciones REST completamente, porque esto lo aleja de encontrar todos los recursos al asecho en su dominio.
Teniendo en cuenta que las aplicaciones reales son más complicadas que los códigos ejemplo en un libro de referencia, veamos que podría pasar si tenemos que modelar retractaciones usando recursos estrictamente. Más que dar seguimiento a la acción retract
sobre el BidsController
, estaríamos obligados a introducir un recurso retraction
, asociado con bids, y escribir un RetractionController
para manejarlo.
resources :bids do
resources :retraction
end
El RetractionController
puede ahora estar a cargo de todo lo que hay que hacer con las actividad de retractación en lugar de tener esta funcionalidad mezclada dentro del BidsController
. Y si usted lo piensa, algo tan pesado como la retractación de una oferta puede acumular algo de lógica. Algunos llaman a separarlos en sus propios controladores una apropiada separación de preocupaciones o incluso buena orientación al objeto.
Los recursos son una abstracción de alto nivel de qué está disponible a través de su aplicación web. Las operaciones de la base de datos simplemente son una de las formas en que almacena y recupera los datos que necesita para generar representaciones de recursos. Pero un recurso REST no necesariamente tiene que asignarse directamente a un controlador, al menos no en teoría. Podría, si quisiera proporcionar servicios REST cuyos identificadores públicos (URI) no coincidan con los nombres de sus controladores.
Lo que todo esto agrega es que podría tener la oportunidad de crear un conjunto de rutas de recursos, y un controlador que coincida, que no se corresponda con ningún modelo de su aplicación. No hay nada de malo en un stack completo recurso/controlador/modelo donde todo coincide por nombre. Pero puede encontrar casos en los que los recursos que está representando se pueden encapsular en un controlador pero no en un modelo.
Un ejemplo en la aplicación de subastas es el controlador de sesiones. Asume un archivo routes.rb
que contiene este línea:
resource :session
Esto mapea el URL /session
al SessionController
como un recurso singleton, aunque no hay modelo Session
. (está apropiadamente definido como recurso singleton porque para la perspectiva del usuario hay sólo una sesión).
Por qué vamos al estilo REST para la autenticación? Si usted lo piensa, las sesiones de usuarios pueden ser creadas y eliminadas. La creación de una sesión toma lugar cuando un usuario se loguea; cuando el usuario sale, la sesión es destruida. La práctica REST de Rails de emparejar una vista y acción new
con una acción create
puede ser seguida! El formulario de login del usuario puede ser el formulario de crear una sesión, depositado en un archivo template como session/new.html.haml
:
%h1 Log in
= form_for :user, url: session_path do |f|
%p
= f.label :login
= f.text_field :login
%p
= f.label :password
= f.password_field :password
%p
= f.submit "Log in"
Cuando el formulario es enviado, el input es manejado por el método create
del controlador de sesiones:
def create
if user.try(:authorize, params[:user][:password])
flash[:notice] = "Welcome, #{user.first_name}!"
redirect_to home_url
else
flash[:error] = "Login invalid"
redirect_to action: "new"
end
end
protected
def user
@user ||= User.find_by(login: params[:user][:login])
end
Nada es escrito en alguna tabla de base de datos en esta acción, pero es digno del nombre create
en virtud del hecho de que crea una sesión. Además, si usted ha decidido en algún punto que esta sesión quedará almacenada en una base de datos, usted ya tiene un nivel de manejo abstracto.
Es bueno permanecer con la mente abierta, entonces, acerca de la posibilidad de CRUD como una filosofía de nombramiento de acciones y CRUD como la operaciones reales de bases de datos pueden ocurrir algunas veces independientemente la una de la otra y la posibilidad de que las facilidades de manejo de recursos en Rails puedan ser asociadas útilmente con un controlador que no corresponde a un modelo. Crear una sesión en el servidor o es una practica que cumple REST, ya que REST pide transferencias sin estado (stateless) de representaciones de recursos. Pero es una buena ilustración de por qué y cómo usted puede hacer el diseño de decisiones envolviendo rutas y recursos que no implican el stack completo de la aplicación.
Si las sesiones son compatibles con REST o no dependen del almacenamiento de la sesión. Lo que REST no permite no es la idea del estado de la aplicación en general, sino la idea del estado del cliente almacenado en el servidor. REST exige que su solicitud esté completa. Por ejemplo, colocar un
auction_id
en un campo oculto de un formulario o en su ruta de acción está bien. Existe un estado en esa request que la acción edit desea pasar a la acción update, y usted la puso en la página, por lo que la siguiente solicitud para actualizar una oferta incluye todo lo que se necesita. Eso es RESTful. Ahora usar campos ocultos no es la única forma de hacer esto. Por ejemplo, no hay problema en usar una cookieuser_id
para la autenticación. Por que? Porque una cookie es parte de un requerimiento. Por lo tanto, estoy muy seguro que las sesiones basadas en cookies son consideradas REST por el mismo principio. Ese tipo de almacenamiento hace sus requerimientos autocontenidos y completos.
Apegarse a los nombres de acciones tipo CRUD es, en general, una buena idea. Así como usted va haciendo un montón de creaciones y destrucciones por todos lados, es fácil pensar en el logeo de un usuario como la creación de una sesión, que lo que se hace para llegar a una completamente nueva categoría semántica para él. Más que el nuevo concepto de log in del usuario, sólo piense en una nueva ocurrencia del viejo concepto de la sesión fue creada.
Uno de los preceptos de REST es que los componentes en un sistema basado en REST intercambian representaciones de recursos. La distinción entre recursos y sus representaciones es vital. Mencionamos esto al comienzo del capítulo, pero no lo exponemos hasta ahora.
Como cliente o consumidor de servicios REST, usted no recupera realmente un recurso desde el servidor; usted recupera una representación de ese recurso. Usted también provee representaciones: el envío de un formulario, por ejemplo, envía al servidor una representación de un recurso, junto a un requerimiento -por ejemplo, PATCH- que dice que esta representación será usada como base para la actualización de un recurso. Las representaciones son la moneda de cambio de la administración de recursos.
La habilidad de retornar diferentes representaciones en las práctica RESTful de Rails esta basada en el método respond_to
en el controlador, el cual, como usted ha visto en los capítulos anteriores, le permite retornar diferentes respuestas dependiendo de lo que el cliente desea. Por otra parte, cuando usted crea rutas de recursos, usted automáticamente obtiene reconocimiento de URLs terminados con un punto más un parámetro :format
.
Por ejemplo, asumiendo que usted tiene resources :auctions
en su archivo de rutas y algo de lógica respond_to
en el ActionsController
como lo siguiente:
def index
@auctions = Auction.all
respond_to do |format|
format.html
format.xml { render xml: @auctions }
end
end
Esto le permite conectarse a este URL: /auctions.xml
.
El ruteo de recursos se asegurará que la acción index
sea ejecutada. Este también reconocerá el .xml
al final de la ruta e interaccionará con respond_to
de acuerdo a esto, retornando la representación XML.
La característica de Responders Gem que fue retirada de Rails 4.2 ofrece una forma más concisa de responder a una variedad de formatos en el mismo controlador.
class AuctionsController < ApplicationController
respond_to :html, :xml, :json
def index
@auctions = Auction.all
respond_with(@auctions)
end
end
Aquí le hemos dicho a nuestro controlador, a nivel de clase, que debería ser un experto en responder a html, xml y json para que cada acción devuelva automáticamente el contenido apropiado.
Cuando llega la solicitud, el respondedor intentará hacer lo siguiente (dada una extensión .json
en la URL):
- Intente hacer render de una vista asociada con una extensión
.json
- Si no existe una vista, llame a
to_json
sobre el objeto pasado arespond_with
- Si el objeto no responde a
to_json
, llame ato_format
sobre él.
Para los recursos anidados y namespaced, debe pasar las dependencias al método respon_to
en forma similar a como generaría una ruta:
respond_with(@user, :managed, @client)
Digamos que usted necesita un link a una representación XML de un recurso. Usted puede lograrlo pasándole un argumento extra a la ruta nombrada REST:
link_to "XML version of this auction", auction_path(@auction, :xml)
Esto generará el siguiente HTML:
<a href="/auctions/1.xml">XML version of this auction</a>
Cuando se siga, este enlace activará el XML, la cláusula del bloque respond_to
en la acción show
del controlador de auctions. Es posible que el XML resultante no se vea mucho en un navegador, pero la ruta indicada está ahí si lo desea.
El circuito está ahora completo: Usted puede generar URLs que apuntan a un tipo específico de respuesta, y usted puede generar requerimientos para diferentes tipos usando respond_to
. Dicho todo, el sistema de ruteo y las facilidades de ruteo de recursos construidas sobre él le dan un conjunto de herramientas poderoso y conciso para diferenciar entre requerimientos y de esta manera, ser capaz de servir diferentes representaciones.
Las facilidades REST de Rails, finalmente se refieren a rutas nombradas y a las acciones de controlador a las cuales apuntan. Mientras más use RESTful de Rails, más conocerá cada una de las siete acciones RESTful. Cómo ellas trabajan en diferentes controladores (y diferentes aplicaciones) es por supuesto algo diferente. Aún, tal vez porque hay un número finito de ellas y sus roles están muy bien delineados, cada una de las siete tiende a tener propiedades muy consistentes y una sensación característica para cada una.
Veremos cada una de las siete acciones con ejemplos y comentarios. Usted las volverá a encontrar, particularmente en el capítulo 4, "Trabajando con controladores", pero aquí usted tendrá algo de historia de respaldo y comenzará a hacer sentido el uso característico de ellas y temas y opciones asociadas con ellas.
Típicamente, una acción index
provee una representación de un recurso plural (o colección). Sin embargo, para ser claros, no todas las colecciones de recursos son mapeadas a la acción index
. Su representación index
por defecto será usualmente genérica, aunque hay mucho que hacer con las necesidades específicas de su aplicación. Pero en general, las acción index
muestra al mundo la representación más neutral posible. Una acción index
muy básica luce así:
class AuctionsController < ApplicationController
def index
@auctions = Auction.all
end
end
El template de vista asociado desplegará información acerca de cada auction, con links a la infomación específica de cada una, y desplegará el perfil de los vendedores.
Usted ciertamente encontrará situaciones donde desee desplegar una representación de una colección en una forma restringida. En nuestro ejemplo recurrente, los usuarios deben estar habilitados para ver una lista de todas sus ofertas, pero quizá usted no quiere que los usuarios vean las ofertas de otras personas.
Hay un par de formas de hacer esto. Una forma es verificar la presencia de un usuario logeado y decidir que hacer basado en eso. Pero eso no va a funcionar aquí. Por una cosa, el usuario logeado desearía ver una vistas más pública. La otra, hay que eliminar o consolidar la mayor dependencia del lado del servidor.
Así que vamos a tratar de ver las dos listas de ofertas, no como versiones públicas y privadas del mismo recurso, sino como diferentes resursos index. La diferencia puede ser reflejada en un ruteo como el siguiente:
resources :auctions do
resources :bids do
get :manage, on: :collection
end
end
resources :bids
Ahora podemos organizar el controlador de bids en forma tal que su acceso está organizado por niveles, usando action callbacks sólo donde es necesario y eliminando la ramificación condicional de las acciones.
class BidsController < ApplicationController
before_action :check_authorization, only: :manage
def index
@bids = Bid.all
end
def manage
@bids = auction.bids
end
protected
def auction
@auction ||= Auction.find(params[:auction_id])
end
def check_authorization
auction.authorized?(current_user)
end
end
Ahora hay una clara distinción entre /bids
y /auctions/1/bids/manage
y el rol que juegan en su aplicación.
En el lado de la ruta nombrada, ahora tenemos bids_url
y manage_auction_bids_url
Hemos preservado la cara pública sin estado del recurso /bids
y resguarda un comportamiento de estado tanto como se puede dentro de un recurso member discreto, /auctions/1/bids/manage
. No se preocupe si esta forma de pensar no viene a usted naturalmente, es parte de la curva de aprendizaje REST.
Si son recursos verdaderamente diferentes, por que no darles a cada uno su propio controlador? Seguramente habrá otras acciones que necesiten ser autorizadas y contextualizadas para el usuario actual.
La acción REST show
es un sabor singular de un recurso. Generalmente se traduce como una representación de la información de un objeto o un miembro de una colección. Al igual que index
, show
es gatillado por un requerimiento GET.
Una acción show
típica -uno podria decir clásica- luce así:
class AuctionController < ApplicationController
def show
@action = Auction.find(params[:id])
end
end
Usted podría querer diferenciar entre perfiles públicamente disponibles, quizá basado en una ruta diferente, y el perfil del usuario actual, el cual puede incluir derechos de modificación y quizá diferente información.
Como con la acción index
, es bueno hacer su acción show
tan pública como sea posible y descargar las vistas administrativas y privilegiadas sobre diferentes controladores o diferentes acciones.
Las acciones destroy
son buenas candidatas para salvaguardas administrativas, aunque por supuesto esto depende de qué esté usted destruyendo. Usted puede desear algo como esto para proteger una acción destroy
:
class ProductsController < ApplicationController
before_action :admin_required, only: :destroy
Una acción destroy
típica puede lucir así:
def destroy
product.destroy
redirect_to products_url, notice: "Product deleted!"
end
Esta aproximación puede ser reflejada en una interface administrativa simple como esta:
% h1 Products
- products.each do |product|
%p = link_to product.name, product
- if current_user.admin?
%p= link_to "delete", product, method: :delete
Este link "delete" aparece dependiendo de si el usuario actual es un administrador.
La API de Rails UJS (unobtrusive JavaScript) simplifica enormemente el HTML emitido para una acción destroy
, usando selectores CSS y vinculando JavaScript (en este caso) al link "delete". Ver Capítulo 19, "AJAX on Rails", para más información acerca de como trabaja esto.
El envío de DELETE
es peligroso. Rails necesita hacerlos de tal forma que sea difícil de gatillarlos accidentalmente -por ejemplo, por un request de envío de crawler o bot a su sitio. Así que cuando usted especifica un método DELETE, el JavaScript que envía un formulario es ligado a su link "delete" con un atributo rel=nofollow
sobre el link. Ya que los bots no envían formularios (y no deben seguir links marcados como "nofollow"), esto le da un nivel de protección a su código.
Como usted ya ha visto, las acciones new
y create
van juntas en RESTful Rails. Un "nuevo recurso" es sólo una entidad que espera ser creada. De acuerdo a esto, la acción new
presenta un formulario y create
crea un nuevo registro, basado en el input del formulario.
Digamos que usted desea un usuario que esté habilitado para crear (es decir, dar inicio a) una subasta. Usted necesitará las siguientes acciones:
- Una acción
new
la cual desplegará el formulario - Una acción
create
la cual creará un nuevo objetoAuction
basado en el input del formulario y procede a una vista (acciónshow
) de la subasta.
La acción new
no tiene que hacer mucho. De hecho, no tiene que hacer nada. Como toda acción vacía, puede incluso ser excluida. Rails averiguará cual vista renderear. Sin embargo, su controlador necesita un método helper de auction, como el siguiente:
protected
def auction
@auction ||= current_user.auctions.build(params[:auction])
end
helper_method :auction
Si esta técnica es marciano para usted no se preocupe, la describiremos en detalle en la sección "Exposición decente" en el Capítulo 10, "Action View"
Un template new.html.haml
simplista puede lucir como el listado 3.2.
Listado 3.2 Un formulario de nueva subasta
%h1 Create a new auction
= form_for auction do |f|
= f.label :subject
= f.text_field :subject
%br
= f.label :description
= f.text_field :description
%br
= f.label :reserve
= f.text_field :reserve
%br
= f.label :starting_bid
= f.text_field :starting_bid
%br
= f.label :end_time
= f.datetime_select :end_time
%br
= f.submit "Create"
Una vez que la información es llenada por un usuario, es momento para el evento principal: la acción create
. A diferencia de new
, esta acción tiene algo que hacer.
def create
if auction.save
redirect_to auction_url(auction), notice: "Auction created!"
else
render :new
end
end
Como new
y create
, las acciones edit
y update
van juntas: edit
provee un formulario y update
procesa el input del formulario.
El formulario para editar un registro aparece similar al formulario para crear uno. (De hecho, usted puede poner mucho de él en un template parcial y usarlo para ambos; lo que se deja como ejercicio)
El método form_for
suficientemente inteligente para checkear si el objeto que usted le pasa ya existe o no. Si es así, entonces reconoce que usted esta editando y especifica un método PATCH sobre el formulario.