Sobre los recursos anidados, REST, y los controladores


#1

Hola gente,

Es la primera vez que me pongo en serio a implementar una
aplicacióndel modo más RESTfull posible y hay alguna cosa con la que no logro
encontrarme cómodo.

Por ejemplo: los recursos anidados. Todavía no logro ver la ventaja de
definir los recursos en el routes como anidados o definirlos como
recursos normales.

Por ejemplo, tenemos Comment que es una comentario de una charla:
Paper. Entiendo que Comment es un recurso anidado de Paper y lo defino
como tal en el routes:

map.resources :papers do |papers|
papers.resources :comments
end

Ahora en el formulario de creación de un comentario:
<% form_for [@paper, @comment] do |f| -%>
<%= f.text_area :text %>
<%= f.submit “Submit”, :disable_with => ‘Submiting…’ %>
<% end -%>

Aquí ya empiezo a fruncir el ceño… vaya modo raro de definir el
action de un formulario. Aunque vale puede quedar más bonito que meter
un campo hidden con el id del paper… vale

Pero llegamos al controlador y ya no puedo hacer esto:
@comment = Comment.new( params[:coment] )

Por que el parámetro ‘paper_id’ ya no está dentro del array
params[:comment] osea que tengo que hacer esto:
@comment =
Comment.new(
:text => params[:comment][:text],
:paper => params[:paper_id]
)

¿Es así como se debe hacer? ¿Hay algo que me pierdo?

Cualquier comentario es bienvenido
f.


#2

Hola Fernando

Lo que yo hago en el controlador es algo así

class Controlador << AC

before_filter :get_parent

def index
@recursos2 = @recurso1.recursos2.find(:all)
end

def new
@recurso2 = @recurso1.recursos2.new

end

def create
@recurso2 = @recurso1.recursos2.new(params[:recurso2])
@recurso2.save

end

private
def get_partent
@recurso1 = Recurso1.find(params[:recurso1_id])
end
end

No se si más o menos ves la idea…


#3

2009/1/3 Fernando G. removed_email_address@domain.invalid

Hola gente,

Es la primera vez que me pongo en serio a implementar una aplicación
del modo más RESTfull posible y hay alguna cosa con la que no logro
encontrarme cómodo.

Por ejemplo: los recursos anidados. Todavía no logro ver la ventaja de
definir los recursos en el routes como anidados o definirlos como
recursos normales.

Si bien no soy un experto en el tema, yo encuento muuuuuy útil ser
RESTfull
aunque siempre termino agregando varios métodos a los originales en las
routes, de todas formas ser RESTfull implica para mi utilizar bien los
verbos HTML que han estado olvidado por un tiempo (put y delete) y la
generación de los path_names te conduce a un estilo de programación
consistente y prolijo.

Con respecto a los “nested resources” me parece que esta bueno porque es
una
forma transparente de modelar este tipo de relaciones.

Por ejemplo, tenemos Comment que es una comentario de una charla:
Paper. Entiendo que Comment es un recurso anidado de Paper y lo defino
como tal en el routes:

map.resources :papers do |papers|
papers.resources :comments
end

yo lo escribiría así:

map.resources :papers do |paper|
paper.resources :comments
end

un campo hidden con el id del paper… vale
)
lo que yo hago, nose porque, seguramente lo hice la primer vez me
funciono y
seguí utilizando esta forma es primero buscar el objeto más externo, no
creo
que tenga ninguna ventaja ni desventaja respecto a tu solución:

@paper = Paper.find(params[:paper_id]
@comment = Comnent.new(:text => …, :paper => @paper)

¿Es así como se debe hacer? ¿Hay algo que me pierdo?

Preguntás porque no te funciona? me parece a mi que está bien usado.

Saludos.


#4

El día 4 de enero de 2009 0:50, Pablo Formoso E.
removed_email_address@domain.invalid
escribió:> Hola Fernando

Lo que yo hago en el controlador es algo así

class Controlador << AC

Me imagino que aquí te refieres a Recurso1Controller.

   end

   def create
           @recurso2 = @recurso1.recursos2.new(params[:recurso2])
           @recurso2.save
           ....
   end

Muy guapo esto de los anidamientos… entendido.

   private
   def get_partent
           @recurso1 = Recurso1.find(params[:recurso1_id])
   end

end

Sipi… lo del get parent también lo tengo, lo eliminé del ejemplo por
claridad.

No se si más o menos ves la idea…

Sipi… gracias…


#5

El día 4 de enero de 2009 1:11, Franco B. removed_email_address@domain.invalid
escribió:

map.resources :papers do |papers|
papers.resources :comments
end

yo lo escribiría así:

map.resources :papers do |paper|
paper.resources :comments
end

Tiene esto alguna implicación… o puedo poner simplemente?:

map.resources :papers do |x|
x.resources :comments
end

@paper = Paper.find(params[:paper_id]
@comment = Comnent.new(:text => …, :paper => @paper)

Si… así lo hago pero me sigue pareciendo peor que antes que lo
solucionaba con un Comment.new(params[:comment])

Pero vamos que lo del anidamiento que comenta Pablo me gusta un poco :slight_smile:

¿Es así como se debe hacer? ¿Hay algo que me pierdo?

Preguntás porque no te funciona? me parece a mi que está bien usado.

Sí… me funciona… preguntaba por que no veía que aportara nada.

Igual no es el rollo REST sino el rollo anidamiento lo que todavía no
le veo ninguna ventaja.

f.


#6

Seguimos con las quejas del anidamiento…

Si queremos borrar un Comment desde un link por ejemplo tengo que poner
esto:
paper_comment_path( @paper, @comment ), :method => :delete

¿Para que me sirve tener localizado el Paper ahí… si luego en el
controlador lo único que miro es el id del Comment?

@comment = comment.find(params[:id])
@comment.destroy

Es una tontería pero mosquea un poco… y el test me queda
así: delete(
:destroy,
:paper_id => papers(:paper1).id,
:id => comment(:comment1).id
)

En vez de
así: delete(
:destroy
:id => comment(:comment1).id
)

Insisto en que creo que mi desconfianza por ahora no es con REST sino
con los recursos anidados.

¿Algún comentario a esto?

f.


#7

El día 4 de enero de 2009 4:21, Daniel R.
Troitiñoremoved_email_address@domain.invalid
escribió:>

Es que al igual que haces @paper.comments.build(params[:comment])
deberías hacer @paper.comments.find(params[:id]) antes de destruir un
objeto, así te aseguras de que no destruyes el comentario de algún
otro paper (imagina que cada paper es de un usuario y un usuario
malintencionado envia un comment id que no es de uno de sus paper).

Oki, entendido…

Gracias Daniel.
f.


#8

2009/1/4 Fernando G. removed_email_address@domain.invalid:

   :id => comment(:comment1).id
 )

Insisto en que creo que mi desconfianza por ahora no es con REST sino
con los recursos anidados.

¿Algún comentario a esto?

f.

Es que al igual que haces @paper.comments.build(params[:comment])
deberías hacer @paper.comments.find(params[:id]) antes de destruir un
objeto, así te aseguras de que no destruyes el comentario de algún
otro paper (imagina que cada paper es de un usuario y un usuario
malintencionado envia un comment id que no es de uno de sus paper).


#9

Es que al igual que haces @paper.comments.build(params[:comment])
deberías hacer @paper.comments.find(params[:id]) antes de destruir un
objeto, así te aseguras de que no destruyes el comentario de algún
otro paper (imagina que cada paper es de un usuario y un usuario
malintencionado envia un comment id que no es de uno de sus paper).

Es un buen punto, aunque no te puedes fiar solamente de esto.

Puede llegarte el id de un paper con un comment que sí le corresponde,
pero ese paper puede que no sea del usuario logado, sino que
malintencionadamente te lo esté enviando. Si quieres resolver ese
problema a base de REST, tendrías que añadir un primer nivel entonces
con el user y anidar los papers dentro… y de todos modos tendrías que
poner un filtro para verificar que el user que te pasan es realmente el
current_user.

Dicho eso… en algunos casos, sí te puede interesar acceder directamente
por el id de un recurso anidado, independientemente del padre. Esto
además tiene mucho más sentido en Rails que en otros entornos, ya que
como ActiveRecord nos “obliga” a usar claves simples, podemos
identificar directamente un recurso por su id, y no necesariamente por
la composición de claves.

Para estos casos, puedes definir las rutas anidadas como “shallow”

En tu caso la ruta quedaría así

map.resources :papers, :shallow=>true do |paper|
paper.resources :comments
end

Esto te permite acceder a un comentario por estas dos urls

/papers/1-mi-documento-que-mola/comments/234

/comments/234

En ambos casos se llama al mismo método de comments (en este caso show)
pero en el primero te llega un paper_id, y en el segundo únicamente el id
del comment

Las shallow permiten dar bastante flexibilidad a la hora de tener
recursos que te interesa consultar anidados a veces, pero sin anidar en
otros casos.

Definiéndolo así, podrías hacer un delete totalmente restful de tu comment sin
necesidad de pasarle el paper al que corresponde.

Más info sobre rutas shallow en
http://guides.rubyonrails.org/routing_outside_in.html

Saludos,


javier ramírez

…i do ruby on rails development in madrid, spain, at
http://www.aspgems.com
…you can find out more about me on http://formatinternet.wordpress.com
and http://workingwithrails.com/person/5987-javier-ramirez


#10

El día 4 de enero de 2009 15:17, javier ramirez removed_email_address@domain.invalid
escribió:

map.resources :papers, :shallow=>true do |paper|
paper.resources :comments
end

Esto te permite acceder a un comentario por estas dos urls

/papers/1-mi-documento-que-mola/comments/234

/comments/234

Justo estaba leyendo esto ayer…

Muchas gracias Javier :slight_smile:

f.


#11

2009/1/4 javier ramirez removed_email_address@domain.invalid:

Puede llegarte el id de un paper con un comment que sí le corresponde,
pero ese paper puede que no sea del usuario logado, sino que
malintencionadamente te lo esté enviando. Si quieres resolver ese
problema a base de REST, tendrías que añadir un primer nivel entonces
con el user y anidar los papers dentro… y de todos modos tendrías que
poner un filtro para verificar que el user que te pasan es realmente el
current_user.

Obviamente. Pero suponía que, en ese caso, @paper lo obtendrías en
algo como current_user.papers.find(params[:paper_id]). Era sólo un
ejemplo incompleto, aunque hablando de seguridad dejar las cosas
incompletas es muy inseguro. Gracias por el toque.

Y sobre lo del tercer nivel de anidamiento, nunca me parece que sea
necesario incluir el usuario como un nivel. Depende de la web, pero si
los usuarios no interaccionan unos con otros, ese nivel es redundante
(@user siempre sería current_user, vamos). En applicaciones donde los
usuarios sean importantes sí que definiría rutas como /usuario/paper a
la vez que /paper (con shallow routes), pero anidar tres veces
/usuario/paper/comentario no me parece una gran idea, prefiero
/paper/comentario. Pero es una opinión.