Recursos anidados en Rails

Los recursos anidados son una de las cosas que mas me gustan de trabajar con REST en Rails y es básicamente la forma de generar rutas mas amistosas de una manera mucho mas rapida asociando los modelos, usando esta forma se crea un lazo inseparable entre el padre y sus hijos.

El efecto lo puedes lograr tu mismo iterando sobre el contenido de los modelos, modificando las rutas, pero el problema es que pierdes el acceso REST a los controladores o al menos se te haga muy muy enredado.

Recuerda, Rails es acerca de convenciones, cuando las sigues te convertirás en uno de los miles de programadores felices, si no… mejor no uses Rails (Obviamente, en ocasiones romper las convenciones ayuda, solo no lo hagas cuando no sea necesario).

Pero como lo implemento


map.resources :branches, :path_prefix => '/admin' do |branches|
branches.resources : orders
end

2do, tienes que agregar las asociaciones

En app/model/branch.rb

has_many : orders # Ojo con las convenciones, has_many :plural

En app/model/order.rb

belongs_to : banch # Ojo con las convenciones, belongs_to :singular (lógico!, se lee!)

3ero. Tenemos que retocar los controladores, para que sepa donde buscar a los hijos.
En este caso app/controllers/orders.rb

class OrdersController < ApplicationController
before_filter : load_branch

# GET /orders
# GET /orders.xml
def index
@orders = @branch.orders.find(:all).reverse

respond_to do |format|
format.html # index.rhtml
format.xml { render : xml => @orders.to_xml }
end
end

# GET /orders/1
# GET /orders/1.xml
def show
@order = @branch.orders.find(params[:id])
@lines = @branch.types

respond_to do |format|
format.html # show.rhtml
format.xml { render : xml => @order.to_xml }
end
end

# GET /orders/new
def new
@order = Order.new
end

# GET /orders/1;edit
def edit
@order = @branch.orders.find(params[:id])
end

# POST /orders
# POST /orders.xml
def create
@order = Order.new(params[:order])

respond_to do |format|
if @order.save
flash[: notice] = ‘Order was successfully created.’
format.html { redirect_to order_url(@branch,@order) }
format.xml { head : created, :location => order_url(@branch,@order) }
else
format.html { render : action => “new” }
format.xml { render : xml => @order.errors.to_xml }
end
end
end

# PUT /orders/1
# PUT /orders/1.xml
def update
@order = @branch.orders.find(params[:id])

respond_to do |format|
if @order.update_attributes(params[:order])
flash[:notice] = ‘Order was successfully updated.’
format.html { redirect_to order_url(@branch,@order) }
format.xml { head : ok }
else
format.html { render :action => “edit” }
format.xml { render : xml => @order.errors.to_xml }
end
end
end

# DELETE /orders/1
# DELETE /orders/1.xml
def destroy
@order = @branch.orders.find(params[:id])
@order.destroy

respond_to do |format|
format.html { redirect_to orders_url }
format.xml { head : ok }
end
end

# Notar como obtengo al papa de las ordenes
def load_branch
@branch = Branch.find params[:branch_id]
end
end

Notar en el código de arriba, como llamo a el padre (before_filter) de en este caso las ordenes, y como genero sus rutas (order_url(@branch,@order))

Lo que automáticamente te genera por ejemplo, las siguientes rutas:

/branches (Lista de sucursales)
/branches/1 (Muestra sucursal 1)
/branches/1;edit (Editas sucursal 1)
/branches/1/orders (Lista de ordenes para la sucursal 1)
/branches/1/orders/1;edit (Editas la orden 1 de la sucursal 1)

ups, pero estoy ocupando path_prefix asi que en realidad quedaría:

(Esta opción es completamente opcional y no recomendada (Es preferible tener una sola interfaz para todo, al menos algunos lo creen así… yo no mucho, aunque creo depende del tipo de proyecto.)

/admin/branches (Lista de sucursales)
/admin/branches/1 (Muestra sucursal 1)
/admin/branches/1;edit (Editas sucursal 1)
/admin/branches/1/orders (Lista de ordenes para la sucursal 1)
/admin/branches/1/orders/1;edit (Editas la orden 1 de la sucursal 1)

Se puede jugar un poco mas….

Aunque lo dejo a tu discreción


map.resources :branches, :path_prefix => '/admin' do |branches|
branches.resources : orders do |orders|
orders.resources :line_items
end
end

Nota: Para este ejemplo ocupe scaffold_resource para generar todo el código, si ustedes también lo hacen podrán verlo funcionando de inmediato.

Nota 2: Lamento lo difícil que es leer código en mi blog, lo tengo como uno de los principales puntos del porque quiero re diseñar mi sitio, pero igualmente espero ayudarlos (ojo: Hay muchos dos puntos ( : ) que separo para no generar emoticons, recuerden es :variable)

Guardado en: aprender, rubyonrails |

4 Comments

  • Thursday, 20 March 2008, 5:58
    By Hugo Videla

    Estimado.
    Te felicito por tu blog, gente como vos son los que suman estos valiosos granitos de arena.

    Estoy medio desorientado con esto de REST, aunque tu ejemplo me clarificó bastante el panorama.

    Te hago una consulta:

    Lo que indicas con :path_prefix => ‘/admin’ es que se trata de un solo controlador “admin” que administra a todos los modelos?

    De ser así, es justo lo que quiero hacer. En caso de no ser, me podrías indicar donde puedo ver ejemplos de esto que quiero hacer?.

    Nuevamente felicitaciones y desde ya muchas gracias!

    Hugo Videla

  • Friday, 21 March 2008, 18:42
    By admin

    No sé si entiendo bien la pregunta pero path_prefix simplemente agrega un prefijo a todas las rutas.

    sin tenias map.resources :funds

    la ruta seria

    http://www.dominio.com/funds

    con map.resources :funds, path_prefix => ‘admins’

    seria

    http://www.dominio.com/admins/funds..

    En realidad no te entiendo bien…

    Espero tus comentarios

  • Tuesday, 1 April 2008, 7:01
    By Hugo Videla

    Te cuento que es lo que quiero hacer:

    Tengo dos modelos, “Padre”, “Hijo”. Mi intención es administrarlos en un mismo controlador para que al momento de mostrar por ejemplo la vista index del padre, pueda “en la misma vista” mostrar sus hijos como así mismo también poder hacer altas bajas y modificaciones de los dos modelos, repito: “en el mismo controlador”.

    Si genero ‘ruby/script scaffold_resource Padre …, y ruby/script generate scaffold_resource Hijo, esto me genera dos controladores idependientes con sus respectivas vistas para cada uno.

    Espero que me hayas entendido, y desde ya, muchas gracias!

    PD: En el caso de que esto no se lleve bien con la filosofía REST, cual sería la forma de lograr esto usando REST?

  • Saturday, 24 May 2008, 15:02

    […] http://www.norellana.com/2007/07/28/recursos-anidados-en-rails/ […]

Leave a comment