ruby-on-rails - elaborando - strong params rails
¿Cómo usar REST con recursos anidados que están representados en XML? (4)
El controlador predeterminado tendrá una línea como
@tree = Tree.new(params[:tree])
que en realidad no analiza automáticamente los parámetros que has enviado. Deseará cambiar su controlador para separar el hashf de params, crear y guardar el árbol, luego crear el nido, utilizando la identificación del árbol (que no se creará hasta después de guardarlo) y guardar el árbol.
¿Claro como el barro?
Mi objetivo es crear recursos anidados a través de una solicitud REST. Las solicitudes REST se representan a través de un documento XML. Eso funciona bien para los recursos individuales, pero no pude gestionarlo para los anidados. OK, les daré un pequeño ejemplo a continuación.
Primero crea un nuevo proyecto de rieles
rails forrest
A continuación, generamos los andamios de dos recursos, los árboles y los nidos de las aves.
./script/generate scaffold tree name:string
./script/generate scaffold bird_nest tree_id:integer bird_type:string eggs_count:integer
En el archivo ./forrest/app/models/tree.rb insertamos la línea "has_many" a continuación porque un árbol puede tener muchos nidos de pájaros :-)
class Tree < ActiveRecord::Base
has_many :bird_nests
end
En el archivo ./forrest/app/models/bird_nest.rb insertamos la línea "belongs_to" a continuación porque cada nido de pájaro debe pertenecer a un árbol.
class BirdNest < ActiveRecord::Base
belongs_to :tree
end
Luego configuramos la base de datos y comenzamos el servidor:
rake db:create
rake db:migrate
./script/server
Simplemente copie y pegue este sniplet XML en un archivo llamado "tree.xml" ...
<tree>
<name>Apple</name>
</tree>
... y publicarlo en el servicio por cURL para crear un nuevo árbol:
curl -H ''Content-type: application/xml'' -H ''Accept: application/xml'' -d @tree.xml http://localhost:3000/trees/ -X POST
Esto funciona bien También para el nido de pájaro XML (nombre de archivo "bird-nest.xml") por separado. Si enviamos esto ...
<bird-nest>
<tree-id>1</tree-id>
<bird-type>Sparrow</bird-type>
<eggs-count>2</eggs-count>
</bird-nest>
... también a través de la siguiente declaración cURL. ¡Ese recurso se crea correctamente!
curl -H ''Content-type: application/xml'' -H ''Accept: application/xml'' -d @bird-nest.xml http://localhost:3000/bird_nests/ -X POST
OK, todo está bien hasta ahora. Ahora viene el punto donde la goma se encuentra con la carretera. Creamos ambos recursos en una sola solicitud. Así que aquí está el XML para nuestro árbol que contiene un nido de pájaro:
<tree>
<name>Cherry</name>
<bird-nests>
<bird-nest>
<bird-type>Blackbird</bird-type>
<eggs-count>2</eggs-count>
</bird-nest>
</bird-nests>
</tree>
Activamos la solicitud apropiada usando cURL nuevamente ...
curl -H ''Content-type: application/xml'' -H ''Accept: application/xml'' -d @tree-and-bird_nest.xml http://localhost:3000/trees/ -X POST
... y ahora obtendremos un error del servidor en el método "generado" (generado) del controlador del árbol: AssociationTypeMismatch (se esperaba BirdNest, se obtuvo Array)
Desde mi punto de vista, esta es la parte importante del registro del servidor con respecto a los atributos recibidos y el mensaje de error:
Processing TreesController#create (for 127.0.0.1 at 2009-02-17 11:29:20) [POST]
Session ID: 8373b8df7629332d4e251a18e844c7f9
Parameters: {"action"=>"create", "controller"=>"trees", "tree"=>{"name"=>"Cherry", "bird_nests"=>{"bird_nest"=>{"bird_type"=>"Blackbird", "eggs_count"=>"2"}}}}
SQL (0.000082) SET NAMES ''utf8''
SQL (0.000051) SET SQL_AUTO_IS_NULL=0
Tree Columns (0.000544) SHOW FIELDS FROM `trees`
ActiveRecord::AssociationTypeMismatch (BirdNest expected, got Array):
/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb:150:in `raise_on_type_mismatch''
/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:146:in `replace''
/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:146:in `each''
/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:146:in `replace''
/vendor/rails/activerecord/lib/active_record/associations.rb:1048:in `bird_nests=''
/vendor/rails/activerecord/lib/active_record/base.rb:2117:in `send''
/vendor/rails/activerecord/lib/active_record/base.rb:2117:in `attributes=''
/vendor/rails/activerecord/lib/active_record/base.rb:2116:in `each''
/vendor/rails/activerecord/lib/active_record/base.rb:2116:in `attributes=''
/vendor/rails/activerecord/lib/active_record/base.rb:1926:in `initialize''
/app/controllers/trees_controller.rb:43:in `new''
/app/controllers/trees_controller.rb:43:in `create''
Entonces mi pregunta es qué estoy haciendo mal con respecto a la anidación de los recursos XML. ¿Cuál sería la sintaxis correcta de XML? ¿O tengo que modificar el controlador del árbol manualmente ya que este caso no está cubierto por el generado?
La anulación del método bird_nests = del árbol modelo es una solución válida, en referencia a la publicación anterior de Patrick Richie (gracias). Entonces, no son necesarios cambios en el controlador. Aquí está el código en detalle que manejará las solicitudes XML dadas mencionadas en el ejemplo anterior (también manejando nidos no de matriz):
def bird_nests=(params)
bird_nest=params[:bird_nest]
if !bird_nest.nil?
if bird_nest.class==Array
bird_nest.each do |attrs|
bird_nests.build(attrs)
end
else
bird_nests.build(bird_nest)
end
end
end
Una forma en que puede lograr esto es anulando el método bird_nests = en su modelo de árbol.
def bird_nests=(attrs_array)
attrs_array.each do |attrs|
bird_nests.build(attrs)
end
end
El único problema aquí es que pierdes el comportamiento predeterminado del colocador, que puede o no ser un problema en tu aplicación.
Si está ejecutando una versión más reciente de Rails, puede activar la asignación masiva tal como se describe aquí:
http://github.com/rails/rails/commit/e0750d6a5c7f621e4ca12205137c0b135cab444a
Y aquí:
http://ryandaigle.com/articles/2008/7/19/what-s-new-in-edge-rails-nested-models
class Tree < ActiveRecord::Base
has_many :bird_nests, :accessible => true
end
Esta es la opcion preferida.
Aunque esta pregunta fue hecha hace dos años y medio, ahora muchas cosas han cambiado: primero en Rails 2.3 con has_many :bird_nests, :accessible => true
y ahora en Rails 3 con accepts_nested_attributes_for
method ... así que estos días en Rails 3 lograría el propósito anterior con el siguiente código:
class Tree < ActiveRecord::Base
has_many :bird_nests
accepts_nested_attributes_for :bird_nests
end
class BirdNest < ActiveRecord::Base
belongs_to :tree
end
Esto genera bird_nests_attributes accessors (getter / setter) para el objeto Tree. Entonces, el xml se vería como sigue:
<tree>
<name>Cherry</name>
<bird_nests_attributes type=''array''>
<bird_nest>
<bird-type>Blackbird</bird-type>
<eggs-count>2</eggs-count>
</bird_nest>
<bird_nest>
<bird-type>Bluebird</bird-type>
<eggs-count>3</eggs-count>
</bird_nest>
</bird_nests_attributes>
</tree>
Rails convertirá el XML anterior al hash de parámetros apropiado ... y el objeto Tree con los objetos bird_nests asociados se creará con las siguientes declaraciones
@tree = Tree.new(params[:tree])
@tree.save
Este es el código mínimo para que funcione. Si está utilizando attr_accessible
en sus modelos, lo cual siempre debe hacer, no olvide agregar :bird_nests_attributes
a attr_accessible
list como los siguientes:
attr_accessible :a_tree_attribute, :another_tree_attr, :bird_nests_attributes
De forma similar, puede agregar validaciones para los atributos a sus respectivos modelos. Y si una validación falla los errores de atributos anidados también estarían disponibles en la lista @tree.errors
. Espero que esto ayude a otros que buscaron la misma pregunta en Google y esta publicación obsoleta fue el resultado principal.