La filosofía REST permite relacionar los recursos entre ellos de manera que los podamos anidar para que únicamente tengan sentido en un contexto determinado. Supongamos que tenemos un blog multiusuario donde hay “users”, “posts” y “comments”. No tiene demasiado sentido acceder a un comentario directamente con una url del tipo /comments/34, ya que un comentario no tiene sentido sin un post, y lo mismo pasa con los posts. Lo ideal sería disponer de una url para acceder a los recursos con un formato como este:
/users/4/posts/34/comments/10
Lo que devolvería el comentario 10 que pertenece al post 34 que a su vez pertence al usuario 4. Una de las ventajas de anidar los recursos es que podemos hacer esto:
/users/30/posts
y nos devuelve todos los posts del usuario 30.
Como véis tiene mucho sentido anidar los recursos para relacionarlos entre ellos. Si queréis saber más sobre recursos anidados podéis consultar:
http://www.norellana.com/2007/07/28/recursos-anidados-en-rails/
http://www.jaimeiniesta.com/2007/12/22/tutorial-recursos-anidados-con-rest-y-rails-2/
Bien, el problema aparece cuando necesitamos tener un recurso que sea anidado en algunas ocasiones y que no lo sea en otras. Me explico. Supongamos una aplicación con empresas que publican ofertas de empleo. Las ofertas de empleo están anidadas a los usuarios ya que una oferta pertenece a una empresa. Así podremos acceder a las ofertas de la siguiente manera
/companies/34/jobs/889
¿Pero que pasa cuando queremos mostrar un listado de todas las ofertas? O lo que es lo mismo, hacer la petición
/jobs
Rails nos va a dar un error ya que necesita saber el id de la empresa para poder listar las ofertas de trabajo. Para poder crear un recurso anidado (o no) según nos convenga hay que hacer lo siguiente:
En el routes.rb crear las rutas
map.resources :companies do |company|
company.resources :jobs
end
map.resources :jobs
Cambiamos el método index del controlador
def index
@jobs = target.find(:all)
end
y añadimos los siguientes métodos
protected
def target
@user ? @user.jobs : Job
end
def get_user
return if params[:user_id].blank?
@user = User.find(params[:user_id])
rescue ActiveRecord::RecordNotFound
redirect_to jobs_url
end
def get_job
@job = Job.find(:all)
end
De esta manera si se accede a una oferta de empleo se ejecutará el método get_user que devolverá el id del usuario para poder acceder al recurso anidado, si se está accediendo directamente al listado de ofertas de empleo, get_user hará un return y se ejecutará el código de get_job, de manera que obtendremos el listado de ofertas de empleo.
De esta manera podemos disponer de un recurso anidado cuando lo necesitemos, pero también accesible directamente para realizar ciertas funciones.
Otro ejemplo es éste que propuso Xavier Noria en la lista de Rails:
Hay que tener presente que las URLs que dan acceso a un mismo recurso
pueden ser multiples, por ejemplo
/releases/my-app-1.1.tar.gz
/releases/latest.tar.gz
Las URLs con caminos que indican jerarquía pueden indicar nesting (pertenencia), scope, y en general lo que tenga sentido en la aplicacion.
Pero supongamos ahora que estás en una central de reservas de hoteles. Para hacer una reserva puede quedar bien una URLS así:
POST /chains/sol-melia/hotel/54/bookings
Eso daría una reserva. En una aplicación web podrías ir al show de esa reserva despues. Si estuvieramos en un web service devolveríamos un 201 Created con una cabecera Location a la dirección de ese recurso nuevo. Esa dirección puede perfectamente ya no reflejar la jerarquia (aunque podría hacerlo y se pueden ofrecer ambos accesos):
GET /bookings/789123
Por ejemplo, cancelar una reserva es más fácil que se ofrezca como
DELETE /bookings/789123
a no ser que el cliente deba añadir la jerarquía que es redundante y puede que le suponga un GET previo. La reserva ya sabe a quien pertenece.
En una API así, BookingsController ha de estar preparado para servir peticiones con scope y sin scope. Si las consultas y las cancelaciones solo son accesibles con URLs directas entonces show y destroy no han
de lidiar con el scope opcional. Si index se publica con y sin scope entonces la acción debe entender ambas llamadas.