5

我的目标是通过一个 REST 请求创建嵌套资源。REST 请求通过 XML 文档表示。这对于单个资源来说很好,但我无法为嵌套资源管理它。好的,接下来我给你举个小例子。

首先新建一个rails项目

rails forrest

接下来我们生成两种资源的脚手架,树木和鸟巢。

./script/generate scaffold tree name:string
./script/generate scaffold bird_nest tree_id:integer bird_type:string eggs_count:integer

在文件 ./forrest/app/models/tree.rb 我们在下面插入“has_many”行,因为一棵树可以有很多鸟巢:-)

class Tree < ActiveRecord::Base
  has_many :bird_nests
end

在文件 ./forrest/app/models/bird_nest.rb 中,我们在下面插入“belongs_to”行,因为每个鸟巢都应该属于一棵树。

class BirdNest < ActiveRecord::Base
  belongs_to :tree
end

之后我们设置数据库并启动服务器:

rake db:create
rake db:migrate
./script/server

只需将此 XML 片段复制并粘贴到名为“tree.xml”的文件中...

<tree>
  <name>Apple</name>
</tree>

...并通过 cURL 将其发布到服务以创建新树:

curl  -H 'Content-type: application/xml' -H 'Accept: application/xml' -d @tree.xml http://localhost:3000/trees/ -X POST

这工作正常。也为燕窝 XML(文件名“bird-nest.xml”)分开。如果我们发送这个...

<bird-nest>
  <tree-id>1</tree-id>
  <bird-type>Sparrow</bird-type>
  <eggs-count>2</eggs-count>
</bird-nest>

...也通过以下 cURL 语句。该资源已正确创建!

curl  -H 'Content-type: application/xml' -H 'Accept: application/xml' -d @bird-nest.xml http://localhost:3000/bird_nests/ -X POST

好的,到目前为止一切都很好。现在到了橡胶与道路相遇的地方。我们在一个请求中创建这两种资源。所以这里是包含一个鸟巢的树的 XML:

<tree>
  <name>Cherry</name>
  <bird-nests>
    <bird-nest>
      <bird-type>Blackbird</bird-type>
      <eggs-count>2</eggs-count>
    </bird-nest>
  </bird-nests>
</tree>

我们再次使用 cURL 触发适当的请求......

curl  -H 'Content-type: application/xml' -H 'Accept: application/xml' -d @tree-and-bird_nest.xml http://localhost:3000/trees/ -X POST

...现在我们将在树的控制器的(生成的)“创建”方法中得到一个服务器错误:AssociationTypeMismatch (BirdNest expected, got Array)

在我看来,这是服务器日志中关于接收到的属性和错误消息的重要部分:

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'

所以我的问题是关于 XML 资源的嵌套我做错了什么。哪个是正确的 XML 语法?还是我必须手动修改树的控制器,因为生成的控制器没有涵盖这种情况?

4

4 回答 4

3

实现此目的的一种方法是覆盖树模型上的 bird_nests= 方法。

def bird_nests=(attrs_array)
  attrs_array.each do |attrs|
    bird_nests.build(attrs)
  end
end

这里唯一的问题是您丢失了 setter 的默认行为,这可能是您的应用程序中的问题,也可能不是问题。

如果您正在运行更新版本的 Rails,您可以按照此处所述打开批量分配:

http://github.com/rails/rails/commit/e0750d6a5c7f621e4ca12205137c0b135cab444a

和这里:

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

这是首选选项。

于 2009-02-17T13:58:57.817 回答
2

覆盖树模型的bird_nests=方法是一个有效的解决方案,参考Patrick Richie的上一篇文章(谢谢)。因此无需对控制器进行任何更改。下面是详细的代码,它将处理上面示例中提到的给定 XML 请求(也处理非数组嵌套):

  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
于 2009-02-17T22:42:09.510 回答
2

虽然这个问题是在两年半前提出的,但现在发生了很多变化:首先是在 Rails 2.3 中has_many :bird_nests, :accessible => true,现在在 Rails 3 中使用accepts_nested_attributes_for方法......所以这些天在 Rails 3 中,您可以使用以下代码实现上述目的:

class Tree < ActiveRecord::Base
  has_many :bird_nests
  accepts_nested_attributes_for :bird_nests
end

class BirdNest < ActiveRecord::Base
   belongs_to :tree
end

这会为 Tree 对象生成bird_nests_attributes 访问器(getter/setter)。所以 xml 如下所示:

<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 会将上述 XML 转换为适当的 params 哈希...并且将使用以下语句创建具有关联的 bird_nests 对象的 Tree 对象

@tree = Tree.new(params[:tree])
@tree.save

这是使其工作的最少代码。如果您attr_accessible在您的模型上使用,您必须始终使用它,然后不要忘记添加:bird_nests_attributesattr_accessible列表中,如下所示:

attr_accessible :a_tree_attribute, :another_tree_attr, :bird_nests_attributes

同样,您可以将属性验证添加到它们各自的模型中。如果验证失败嵌套属性错误也将在@tree.errors列表中可用。希望这会帮助其他在 google 中搜索相同问题的人,并且这个过时的帖子是最高结果。

于 2011-08-21T01:30:08.100 回答
1

默认控制器将有一行

@tree = Tree.new(params[:tree])

它显然不会自动解析您发送的参数。您需要更改控制器以分离参数 hashf,创建并保存树,然后使用树的 ID(在保存后才会创建)创建巢,然后保存树。

清如泥?

于 2009-02-17T13:33:24.650 回答