Forum: Rails-ES sobre los recursos anidados, REST, y los controladores

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
6f952bee7570a3db2ecba5b06c0062b3?d=identicon&s=25 Fernando Guillen (fguillen)
on 2009-01-04 00:35
(Received via mailing list)
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.
33a24a134536b312b0d5334c2a9152db?d=identicon&s=25 Pablo Formoso Estrada (Guest)
on 2009-01-04 00:48
(Received via mailing list)
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...
B8a441944cd888fa73065dc5a6bb4237?d=identicon&s=25 Franco Brusatti (francob)
on 2009-01-04 01:11
(Received via mailing list)
2009/1/3 Fernando Guillen <fguillen.mail@gmail.com>

> 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.
6f952bee7570a3db2ecba5b06c0062b3?d=identicon&s=25 Fernando Guillen (fguillen)
on 2009-01-04 01:29
(Received via mailing list)
El día 4 de enero de 2009 0:50, Pablo Formoso Estrada
<pablo@pabloformoso.com>
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..
6f952bee7570a3db2ecba5b06c0062b3?d=identicon&s=25 Fernando Guillen (fguillen)
on 2009-01-04 01:32
(Received via mailing list)
El día 4 de enero de 2009 1:11, Franco Brusatti <fbrusatti@gmail.com>
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  :)

>
>> ¿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.
6f952bee7570a3db2ecba5b06c0062b3?d=identicon&s=25 Fernando Guillen (fguillen)
on 2009-01-04 01:38
(Received via mailing list)
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.
49b6123803e4f327144e991daab62f77?d=identicon&s=25 Daniel Rodriguez Troitiño (Guest)
on 2009-01-04 04:23
(Received via mailing list)
2009/1/4 Fernando Guillen <fguillen.mail@gmail.com>:
>
>        :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).
6f952bee7570a3db2ecba5b06c0062b3?d=identicon&s=25 Fernando Guillen (fguillen)
on 2009-01-04 13:19
(Received via mailing list)
El día 4 de enero de 2009 4:21, Daniel Rodriguez
Troitiño<notzcoolx@yahoo.es>
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.
1f2eadfb41362800ebc2cf211b91d0f7?d=identicon&s=25 javier ramirez (Guest)
on 2009-01-04 15:17
(Received via mailing list)
>> 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
6f952bee7570a3db2ecba5b06c0062b3?d=identicon&s=25 Fernando Guillen (fguillen)
on 2009-01-04 16:03
(Received via mailing list)
El día 4 de enero de 2009 15:17, javier ramirez <jramirez@aspgems.com>
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 :)

f.
49b6123803e4f327144e991daab62f77?d=identicon&s=25 Daniel Rodriguez Troitiño (Guest)
on 2009-01-04 16:48
(Received via mailing list)
2009/1/4 javier ramirez <jramirez@aspgems.com>:
> 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.
This topic is locked and can not be replied to.