Rails: Как транзакционно добавить ассоциацию has_many к существующей модели?

Давайте представим, что я управляю воображаемым художественным магазином с парой моделей (и под моделями я имею в виду термин Rails, а не термин искусства, как в случае обнаженных моделей), который выглядит примерно так:

class Artwork < ActiveRecord::Base
  belongs_to :purchase
  belongs_to :artist
end

class Purchase < ActiveRecord::Base
  has_many :artworks
  belongs_to :customer
end

Создается файл Artwork, который через некоторое время включается в файл Purchase. В моем методе контроллера create или update для Purchase я хотел бы связать новый Purchase с существующим Artwork.

Если бы Artwork не существовало, я мог бы сделать @purchase.artworks.build или @purchase.artworks.create, но оба они предполагают, что я создаю новое Artwork, которым я не являюсь. Я мог бы добавить существующую иллюстрацию примерно так:

params[:artwork_ids].each do |artwork|
  @purchase.artworks << Artwork.find(artwork)
end

Однако это не транзакция. База обновляется мгновенно. (Если, конечно, я не нахожусь в контроллере create, в этом случае я думаю, что это может быть сделано «транзакционно», поскольку @purchase не существует, пока я не вызову save, но это не поможет мне для update.) @purchase.artwork_ids= метод, но он также немедленный.

Я думаю, что-то подобное будет работать для действия update, но это очень неэлегантно.

@purchase = Purchase.find(params[:id])
result = @purchase.transaction do
  @purchase.update_attributes(params[:purchase])
  params[:artwork_ids].each do |artwork|
    artwork.purchase = @purchase
    artwork.save!
  end
end

За этим следует обычное:

if result 
  redirect_to purchase_url(@purchase), notice: 'Purchase was successfully updated.' }
else
  render action: "edit"
end

То, что я ищу, это что-то вроде того, как это работало бы с другого направления, когда я мог бы просто поместить accepts_nested_attributes_for в свою модель, а затем вызвать result = @artwork.save, и все работает как по волшебству.


person Geoff    schedule 04.12.2012    source источник


Ответы (2)


Я нашел способ делать то, что хочу, довольно элегантный. Мне нужно было обновить каждую часть моего Product MVC.

Модель:

attr_accessible: artwork_ids

Мне пришлось добавить artwork_ids в attr_accessible, так как раньше он не был включен.

Вид:

= check_box_tag "purchase[artwork_ids][]", artwork.id, artwork.purchase == @purchase

На мой взгляд, у меня есть массив для каждого произведения искусства с check_box_tag. Я не мог использовать check_box из-за того, что отсутствие флажка могло привести к отправке скрытого значения «true» вместо идентификатора произведения искусства. Однако это оставляет мне проблему удаления всех работ из покупки. Если при выполнении update я сниму все флажки, то в хэше params[:purchase] не будет записи :artwork_ids.

Контроллер:

params[:purchase][:artwork_ids] ||= []

Добавление этого гарантирует, что значение установлено, и будет иметь желаемый эффект удаления всех существующих ассоциаций. Однако это приводит к надоедливой ошибке rspec Purchase.any_instance.should_receive(:update_attributes).with({'these' => 'params'}), так как :update_attributes фактически получил {"these"=>"params", "artwork_ids"=>[]}). Вместо этого я попытался установить hidden_value_tag в представлении, но не смог заставить его работать. Я думаю, что эта гнида достойна нового вопроса.

person Geoff    schedule 05.12.2012
comment
Если подумать, = hidden_field_tag "purchase[artwork_ids][]", кажется, работает. - person Geoff; 05.12.2012
comment
Я только что понял, что это в основном Railscast 17. Я почти уверен, что даже смотрел его раньше, но я думаю, что забыл об этом, потому что он использовал HABTM. - person Geoff; 05.12.2012

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

Вот пример для вашего варианта использования.

Модель клиента

    has_many :purchases

    has_many :artwork, :through => :purchase

Художественная модель

    has_many :purchases

    has_many :customers, :through => :purchase

Модель покупки

    belongs_to :customer
    belongs_to :artwork

Модель покупки должна содержать customer_id и artwork_id.

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

Когда покупатель нажимает кнопку покупки, создается новый объект покупки, который включает в себя customer_id и artwork_id. Это позволяет вам создать ассоциацию между покупателем и произведением искусства, которое он покупает. У вас также может быть столбец price_paid, чтобы сохранить цену, которую клиент заплатил во время покупки.

если вам нужна дополнительная помощь, вы можете изучить объединение многих ко многим ассоциациям, используя :through.

Надеюсь, поможет

person Richard Lau    schedule 04.12.2012
comment
Большое спасибо за предложение. К сожалению, мне действительно нужно, чтобы покупка включала в себя несколько разных произведений искусства, а это значит, что мне нужно, чтобы Purchase было has_many. - person Geoff; 05.12.2012
comment
хорошо, вы можете использовать драгоценный камень тележки, но если вы хотите сделать это с нуля. я бы сделал модель корзины, в которой вы можете добавить в корзину несколько произведений искусства, а затем, когда клиент действительно покупает товары в корзине, вы можете связать заказ с клиентом и показать несколько произведений искусства. - person Richard Lau; 05.12.2012