Rails: How do I transactionally add a has_many association to an existing model?

霸气de小男生 提交于 2019-12-11 02:37:09

问题


Let's imagine I run an imaginary art store with a couple models (and by models I'm referring to the Rails term not the arts term as in nude models) that looks something like this:

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

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

The Artwork is created and sometime later it is included in a Purchase. In my create or update controller method for Purchase I would like to associate the new Purchase with the existing Artwork.

If the Artwork did not exist I could do @purchase.artworks.build or @purchase.artworks.create, but these both assume that I'm creating a new Artwork which I am not. I could add the existing artwork with something like this:

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

However, this isn't transactional. The database is updated immediately. (Unless of course I'm in the create controller in which case I think it may be done "transactionally" since the @purchase doesn't exist until I call save, but that doesn't help me for update.) There is also the @purchase.artwork_ids= method, but that is immediate as well.

I think something like this will work for the update action, but it is very inelegant.

@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

This would be followed by the conventional:

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

What I'm looking for is something like the way it would work from the other direction where I could just put accepts_nested_attributes_for in my model and then call result = @artwork.save and everything works like magic.


回答1:


I have figured out a way to do what I want which fairly elegant. I needed to make updates to each part of my Product MVC.

Model:

attr_accessible: artwork_ids

I had to add artwork_ids to attr_accessible since it wasn't included before.

View:

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

In my view I have an array for each artwork with a check_box_tag. I couldn't use check_box because of the gotcha where not checking the box would cause a hidden value of "true" to be sent instead of an artwork id. However, this leaves me with the problem of deleting all the artwork from a purchase. When doing update, if I uncheck each check box, then the params[:purchase] hash won't have an :artwork_ids entry.

Controller:

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

Adding this guarantees that the value is set, and will have the desired effect of removing all existing associations. However, this causes a pesky rspec failure Purchase.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) fails because :update_attributes actually received {"these"=>"params", "artwork_ids"=>[]}). I tried setting a hidden_value_tag in the view instead, but couldn't get it to work. I think this nit is worthy of a new question.




回答2:


It is probably best to use make the purchase model a join table and have many to many associations.

Here is an example for your use case.

Customer model

    has_many :purchases

    has_many :artwork, :through => :purchase

Artwork model

    has_many :purchases

    has_many :customers, :through => :purchase

Purchase model

    belongs_to :customer
    belongs_to :artwork

The purchase model should contain customer_id and artwork_id.

you would also need to create a purchase controller that allows you create a new purchase object.

When a customer presses the purchase button it would create a new purchase object which includes the customer_id and the artwork_id. This allows you to create an association between the customer and the artwork they purchase. You can also have a price_paid column to save the price the customer paid at the time of purchase.

if you need more help you can research join many to many associations using :through.

hope it helps



来源:https://stackoverflow.com/questions/13709624/rails-how-do-i-transactionally-add-a-has-many-association-to-an-existing-model

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!