Duplicate records created in has_many :through associations
I have three models: Booking, ExtraNight and BookedExtraNight. Running Rails 2.3.11
Booking:
has_many :extra_nights, :through => :booked_extra_nights
has_many :booked_extra_nights, :dependent => :destroy
ExtraNight:
has_many :booked_extra_nights, :dependent => :destroy
has_many :bookings, :through => :booked_extra_nights
BookedExtraNight:
belongs_to :extra_night
belongs_to :booking
Now because of the way the application works the booking exists when the booked_extra_night is created. A booked_extra_night is created with the Booking Update action. In the view the booked_extra_night is built using:
<% unless @booking.booked_extra_nights.exists? %>
<% @booking.booked_extra_nights.build %>
<% end %>
I use nested_form_for @booking and f.fields_for :booked_extra_nights to create the booked_extra_nights.
Now everything works fine until I press submit (update action) when two copies of the booked_extra_night is create.
See log:
开发者_StackOverflow中文版Processing BookingsController#update (for 127.0.0.1 at 2011-02-21 07:44:22) [PUT]
Parameters: {"action"=>"update", "_method"=>"put", "authenticity_token"=>"b/M+VjMxA8RFqbubhBeF494B/zhxi/2Eb3EtoCoRLx0=", "id"=>"5b2jwg7qw5na3vz4nt", "booking"=>{"booked_extra_nights_attributes"=>{"0"=> {"number_of_days"=>"2", "from_date(1i)"=>"2011", "from_date(2i)"=>"9", "from_date(3i)"=>"1", "_destroy"=>"", "extra_night_id"=>"7"}}}, "controller"=>"bookings"}
Booking Load (1.3ms) SELECT * FROM "bookings" WHERE ("bookings"."random_url_key" = '5b2jwg7qw5na3vz4nt') LIMIT 1
Variant Load (0.6ms) SELECT * FROM "variants" WHERE ("variants"."id" = 27)
SQL (0.1ms) BEGIN
SQL (0.7ms) INSERT INTO "booked_extra_nights" ("number_of_days", "created_at", "updated_at", "booking_id", "from_date", "extra_night_id") VALUES(2, '2011-02-21 06:44:22.525154', '2011-02-21 06:44:22.525154', 69, '2011-09-01', 7) RETURNING "id"
SQL (0.8ms) COMMIT
SQL (0.6ms) BEGIN
SQL (0.6ms) INSERT INTO "booked_extra_nights" ("number_of_days", "created_at", "updated_at", "booking_id", "from_date", "extra_night_id") VALUES(2, '2011-02-21 06:44:22.544452', '2011-02-21 06:44:22.544452', 69, '2011-09-01', 7) RETURNING "id"
SQL (25.8ms) COMMIT
SQL (0.1ms) BEGIN
Booking Update (0.6ms) UPDATE "bookings" SET "updated_at" = '2011-02-21 06:44:22.575409', "aasm_state" = 'step3' WHERE "id" = 69
SQL (0.5ms) COMMIT
Redirected to http://localhost:3000/bookings/5b2jwg7qw5na3vz4nt/step3
As you can see two identical records is created, now if I were to build 4 booked_extra_nights and press submit I'd end up with 8 records.
I've also found out that if I create a booked_extra_night record at the same time the booking is created then I can add as many as I want doing the above without duplicates. This happens on all 2.3.x versions of rails as far as I know, so it's obviously something I'm doing wrong. Any help would be greatly appreciated since it's doing my head in.
Thanks!
I've had this problem as well, and my solution was in my controller, to not use the build, and to use new and supply the parent parameter.
Extract in my code.. Instead of using
@new_brand = @company.brands.build/new
I use
@new_brand = Brand.new(:company => @company)
The first adds this object to the @company as an empty object, then when you submit its create another new one within your create action.
The second just creates a new object in memory for the purposes of the form, but when the values are submitted to the create action, there are no objects tied to the company
Thanks, Rabbot! You got me on the right track, sad to say, but the update action were a mess. I've re-factored it and now everything works, I think the double saves had to do with me first updating the booking, then redirecting, then updating the booking again through AASM.
Below is the old update action (I told you it was a mess):
def update
@booking = Booking.find_by_random_url_key(params[:id])
@variant = @booking.variant
if params[:back_button]
if @booking.aasm_state == "step2"
redirect_to booking_step1_url(@booking)
elsif @booking.aasm_state == "step3"
redirect_to booking_step2_url(@booking)
elsif @booking.aasm_state == "step4"
redirect_to booking_step3_url(@booking)
elsif @booking.aasm_state == "step5"
redirect_to booking_step4_url(@booking)
end
@booking.previous!
else
if @booking.update_attributes(params[:booking]) && @booking.aasm_state == "step1"
redirect_to booking_step2_url(@booking)
@booking.next!
elsif @booking.update_attributes(params[:booking]) && @booking.aasm_state == "step2"
@booking.next!
redirect_to booking_step3_url(@booking)
elsif @booking.update_attributes(params[:booking]) && @booking.aasm_state == "step3"
redirect_to booking_step4_url(@booking)
@booking.next!
elsif @booking.update_attributes(params[:booking]) && @booking.aasm_state == "step4"
redirect_to booking_url(@booking)
@booking.next!
end
end
end
And this is the new, re-factored update action.
def update
@booking = Booking.find_by_random_url_key(params[:id])
@variant = @booking.variant
if params[:back_button]
@booking.previous!
redirect_to :controller => "bookings", :action => "#{@booking.aasm_state}", :id => @booking
else
@booking.update_attributes(params[:booking])
@booking.next!
redirect_to :controller => "bookings", :action => "#{@booking.aasm_state}", :id => @booking
end
end
Same functionality, many many lines less. And it does not create duplicate extra nights.
Thanks, Rabbot. Your answer made me think, as I'd tried changing stuff in the view and model several times without success. I just assumed the controller worked since it worked for everything else.
精彩评论