Aunque el tema de REST y ActiveResource está disponible en Rails 1.2, hasta ahora ha sido un tema que he ido posponiendo en mi lista de cosas por aprender.
La introducción de REST por defecto en la generación de scaffolds de Rails 2 ha cambiado las cosas, y me ha hecho ver que no puedo dejar para más adelante el actualizarme a la filosofía REST.
He estado haciendo algunas pruebas para aclarar las ideas, así que aquí os dejo los códigos para que podáis echarle un vistazo, aprender conmigo y dejarme comentarios… seguro que he pasado por alto algunas cosas.
El objetivo de este tutorial es practicar los recursos anidados. Queremos construir una aplicación típica, un blog, pero en este caso, multiusuario. Tendremos N usuarios, cada uno de los cuales tiene M posts, y cada post tiene Z comentarios. Lo que buscamos es poder definir rutas REST anidadas, de manera que cada recurso (usuario, post, comentario) sólo tenga sentido dentro del contexto de su padre. En concreto, queremos rutas como estas:
# Listado de usuarios /users # Un usuario determinado /users/:user_id # Posts de un usuario determinado /users/:user_id/posts # Un post determinado de un usuario determinado /users/:user_id/posts/:post_id # Comentarios de un post y usuario determinados /users/:user_id/posts/:post_id/comments # Comentario determinado de un post y usuario determinados /users/:user_id/posts/:post_id/comments/:comment_id
Como se ve, son rutas muy limpias y autoexplicativas.
Os explico en rasgos generales lo que hay que hacer para conseguir esto. Tras crear nuestra aplicación y la base de datos, generaremos los 3 scaffolds para controlar nuestros 3 recursos (users, posts y comments):
rails blog cd blog rake db:create script/generate scaffold User name:string script/generate scaffold Post title:string body:text user_id:integer script/generate scaffold Comment body:text post_id:integer rake db:migrate
Los scaffolds generarán los modelos y migraciones, controladores y vistas, etc. Además, introducirán rutas REST por defecto para cada modelo, pero las tenemos que cambiar porque no queremos que sean independientes, sino anidadas. Esto es, no queremos gestionar un post fuera del contexto de su usuario, ni un comentario fuera del contexto de su post y del usuario de este post.
Para ello, edita config/routes.rb, y elimina estas 3 líneas:
map.resources :comments map.resources :posts map.resources :users
Y pon en su lugar esta ruta anidada:
map.resources :users do |user|
user.resources :posts do |post|
post.resources :comments
end
end
Ahora, edita los modelos e introduce la relación entre ellos como de costumbre en Rails:
class User < ActiveRecord::Base has_many :posts, :dependent => :destroy end class Post < ActiveRecord::Base belongs_to :user has_many :comments, :dependent => :destroy end class Comment < ActiveRecord::Base belongs_to :post end
Ahora, tendremos que modificar los controladores y vistas, porque fueron generados sin tener en cuenta que estarían anidados, así que hemos de actualizarlos. El controlador de usuarios (user_controller.rb) no necesita cambios porque es independiente de los de posts y comentarios.
Donde sí necesitamos cambios es en el controlador de posts. En primer lugar, necesitamos asegurar que un post sólo tiene sentido dentro del contexto de un usuario determinado, que se habrá pasado en la URL. Así que usaremos un filtro before_filter para que, antes de realizar cualquier acción, coja este usuario:
before_filter(:get_user)
El método get_user lo definiremos así, dentro del apartado de métodos privados ya que no se usa más que desde dentro de este controlador:
private def get_user @user = User.find(params[:user_id]) end
Ahora, modificaremos el resto de los métodos para que no se busquen los posts en general, sino dentro del contexto del usuario. Por ejemplo, en el método index, que lista los posts, en lugar de mostrarlos todos con:
@posts = Post.find(:all)
Mostraremos sólo los de ese usuario:
@posts = @user.posts.find(:all)
Modificaremos de manera similar el resto de acciones, para buscar el post a mostrar, crear, modificar o eliminar dentro de la colección de posts del usuario. En lugar de:
@post = Post.find(params[:id])
Usaremos:
@post = @user.posts.find(params[:id])
Esto nos añade seguridad ya que por ejemplo para editar un post, hay que indicar no sólo su id sino también su user_id, y si no son válidos, no lo encontrará.
Dentro del controlador también hay que cambiar las redirecciones, ya que por ejemplo tras crear un post, para redirigir al “show” de este post venía así recién creado el scaffold:
redirect_to(@post)
Ahora, como se trata de un recurso anidado dentro del usuario, hay que indicarle la ruta completa así:
redirect_to(user_post_url(@user, @post))
Echa un vistazo a los códigos de ejemplo que adjunto en este tutorial para ver el resto de modificaciones en este controlador.
El controlador de comentarios (comment_controller.rb) necesitará modificaciones similares, pero esta vez además del usuario hemos de recuperar el post, para tener el contexto completo. Es muy similar al ejemplo anterior, primero usamos los siguientes before_filter:
before_filter(:get_user) before_filter(:get_post)
Y los definimos así:
private def get_user @user = User.find(params[:user_id]) end def get_post @post = User.find(params[:user_id]).posts.find(params[:post_id]) end
Fíjate en que podría haber cogido el post directamente pero para mayor seguridad lo estoy cogiendo a través del usuario.
Las redirecciones también serán similares. Por ejemplo, tras crear un comentario y redirigir al comentario, sería así:
redirect_to(user_post_comment_url(@user, @post, @comment))
Como ves, es similar pero indicando el usuario y post y comentario a que quieres redirigir.
También será necesario que edites todas las vistas de posts y comments, ya que los helpers generados por el scaffold no te valen, y has de indicar el contexto en todos. Por ejemplo, el enlace para crear un nuevo comentario será así ahora:
link_to 'New comment', new_user_post_comment_path(@user, @post)
Eso es todo por mi parte… Os dejo a continuación los códigos de este ejemplo para que podáis ver cómo he construido el resto de rutas con la ayuda de los helpers de Rails 2.0.2.
Códigos del blog rest con rutas anidadas
Seguro que he cometido unas cuantas correcciones, por lo que os agradecería que me lo indicarais con vuestros comentarios.
9 comments ↓
Buenas Jaime,
He visto un comentario tuyo en la web de la gente de Güebs cuando buscaba un hosting interesante…
¿alguna novedad? Por lo que parece va bastante bien no? Me recomiendas su servicios?
Bueno, un saludo!! Nos vemos.
David
Por cierto,
Yo tampoco había tenido tiempo de dedicarme al rema Rest i con Rails 2 veo que no tengo más remedio que ponerme a ello.
Muy buen post, al menos a mí me ha servido de introducción al tema!
Hola David, me alegro de que te haya servido mi tutorial.
Sobre guebs.com, estoy muy contento con ellos. Te los recomiendo si buscas hosting compartido con Rails, desde luego son mucho mejor que Dreamhost. Allí tengo alojado http://www.nurbijou.com/
Yo ahora también estoy probando con un dedicado virtualizado en Slicehost: http://www.borispider.com
[...] http://www.jaimeiniesta.com/2007/12/22/tutorial-recursos-anidados-con-rest-y-rails-2/ [...]
Emili ha recopilado más ejemplos relacionados con recursos REST anidados y no anidados:
http://www.abecedata.com/2008/01/08/recursos-anidados-o-no-en-rest/
Estoy trabajando en un pequeño proyecto y mis compañeros me enseñaron eso de las rutas rest, pero no me hablaron mucho sobre el tema de los recursos anidados, es más, no los tengo anidados.
La verdad es que me gusta la idea, sobre todo porque en este proyecto muchos cosas dependen de muchas y aplicándole esa lógica puede ser más sencillo y seguro realizar el programa.
Gracias de nuevo
Muy bueno el tutorial…, te cuento que soy novato en Rails y te quiero hacer una consulta:
Las rutas REST se van armando con la relación etre modelos Padre, Hijos, Nietos como en tu ejemplo. Y la lógica de cada uno de éstos está implementada por su respectivo controlador.
Ahora bien, que sucede si quiero administrar estos tres modelos desde un solo controlador?, como se puede implementar usando REST?, me explico?.
En este caso por ejemplo para acceder a un comments de un usuario determinado de un post determinado ,no voy a poder hacer como tu explicas:
redirect_to(user_post_comment_url(@user, @post, @comment))
ya que solo poseo un controlador!
O estoy loco?
Desde ya muchas gracias y felicitaciones por tu aporte que me clarificó un montón!.
Hola Hugo, aclararte que puedes tener varios controladores que manejen diferentes modelos. En mi ejemplo, la redirección te mantendría dentro de la estructura /usuario/posts/comentarios pero también podrías tener además un controlador, por ejemplo, /novedades que mostrara los últimos usuarios, últimos comentarios, etc…
La cosa está en pensar si necesitas o no rutas REST, y en ese caso si las quieres anidadas o no… todo esto no quita para que además tengas otros controladores que hagan otras cosas…
[...] http://www.jaimeiniesta.com/2007/12/22/tutorial-recursos-anidados-con-rest-y-rails-2/ [...]
Leave a Comment