问题
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