Как использовать REST с вложенными ресурсами, которые представлены в XML? - PullRequest
5 голосов
/ 17 февраля 2009

Моя цель - создать вложенные ресурсы с помощью одного запроса REST. Запросы REST представлены в виде XML-документа. Это хорошо работает для отдельных ресурсов, но я не мог управлять этим для вложенных. Хорошо, я приведу небольшой пример в следующем.

Сначала создайте новый проект рельсов

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 мы вставляем строку «принадлежать» ниже, потому что гнездо каждой птицы должно принадлежать дереву.

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, получен массив)

На мой взгляд, это важная часть журнала сервера относительно полученных атрибутов и сообщения об ошибке:

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 ]

3 голосов
/ 17 февраля 2009

Один из способов сделать это - переопределить метод bird_nests = в вашей модели дерева.

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

Единственная проблема заключается в том, что вы теряете поведение по умолчанию для сеттера, что может быть или не быть проблемой в вашем приложении.

Если вы используете более свежую версию 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

Это предпочтительный вариант.

2 голосов
/ 21 августа 2011

Хотя этот вопрос задавался два с половиной года назад, сейчас многое изменилось: сначала в 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

Генерирует методы доступа bird_nests_attributes (getter / setter) для объекта Tree. Таким образом, 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 ... и объект Tree со связанными объектами bird_nests будет создан с помощью следующих операторов

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

Это минимальный код, чтобы заставить его работать. Если вы используете attr_accessible в своих моделях, что вы всегда должны делать, не забудьте добавить :bird_nests_attributes в список attr_accessible, как показано ниже:

attr_accessible :a_tree_attribute, :another_tree_attr, :bird_nests_attributes

Аналогичным образом вы можете добавить проверки для атрибутов в их соответствующие модели. И если проверка не пройдена, вложенные атрибуты также будут доступны в списке @tree.errors. Надеюсь, что это поможет другим, кто искал тот же вопрос в Google, и этот устаревший пост стал лучшим результатом.

2 голосов
/ 18 февраля 2009

Переопределение метода bird_nests = древовидной модели является правильным решением, ссылаясь на предыдущий пост Патрика Ричи (спасибо). Поэтому никаких изменений на контроллере не требуется. Вот подробный код, который будет обрабатывать заданные 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
1 голос
/ 17 февраля 2009

Контроллер по умолчанию будет иметь строку типа

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

, который автоматически не анализирует отправленные вами параметры. Вы захотите изменить свой контроллер, чтобы выделить этот хэш-параметр, создать и сохранить дерево, затем создать гнездо, используя идентификатор дерева (который не будет создан до тех пор, пока вы его не сохраните), и сохранить дерево.

Ясно, как грязь?

...