Reputation: 489
I am using the cocoon gem for nested forms. An accommodation_category can have many options. I made a form structure where users first create the accommodation_category and thereafter create options via a custom controller action.
Everything works as expected, however the nested option object(s) do(es) not save in my DB. I used cocoon for several other nested objects related to accommodation_category, however the options object is the only one which is not saving. I checked for 2 days, but cannot seem to find the error/typo in my code.
When running in my local environment I obtain a rollback when arrived at my accommodation_category.rb line 32
@accommodation_category = @accommodation_category.update_attributes(accommodation_category_params)
TEXT TERMINAL RUNNING LOCALHOST:
"Started PATCH "/parks/19/accommodation_categories/132" for ::1 at 2019-09-09 10:45:05 +0200
Processing by AccommodationCategoriesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"X+BFZCfDrY2iGq+wH4H4c/XieKoVaLMUTZlNUaNb65OAxcm09fw2HyKkU2biQttzEsIzL87FIJFOFENpvc652Q==", "accommodation_category"=>{"options_attributes"=>{"1568018703588"=>{"name"=>"1", "description"=>"", "_destroy"=>"false"}}}, "commit"=>"Save", "park_id"=>"19", "id"=>"132"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 2], ["LIMIT", 1]]
↳ /Users/xx/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98 Park Load (0.2ms) SELECT "parks".* FROM "parks" WHERE "parks"."id" = $1 LIMIT $2 [["id", 19], ["LIMIT", 1]]
↳ app/controllers/accommodation_categories_controller.rb:29 AccommodationCategory Load (0.4ms) SELECT "accommodation_categories".* FROM "accommodation_categories" WHERE "accommodation_categories"."park_id" = $1 AND "accommodation_categories"."id" = $2 LIMIT $3 [["park_id", 19], ["id", 132], ["LIMIT", 1]]
↳ app/controllers/accommodation_categories_controller.rb:30 (0.2ms) BEGIN
↳ app/controllers/accommodation_categories_controller.rb:32 (0.1ms) ROLLBACK
↳ app/controllers/accommodation_categories_controller.rb:32 AccommodationCategory Load (0.2ms) SELECT "accommodation_categories".* FROM "accommodation_categories" WHERE "accommodation_categories"."park_id" = $1 AND "accommodation_categories"."id" = $2 LIMIT $3 [["park_id", 19], ["id", 132], ["LIMIT", 1]]
↳ app/controllers/accommodation_categories_controller.rb:44
Redirected to http://localhost:3000/accommodation_categories/132/new_discounts Completed 302 Found in 19ms (ActiveRecord: 1.4ms) "
END TEXT TERMINAL RUNNING LOCALHOST
Please find below the code of the app:
accommodation_category.rb
class AccommodationCategory < ApplicationRecord
belongs_to :park
has_many :options, inverse_of: :accommodation_category, dependent: :destroy
accepts_nested_attributes_for :options, allow_destroy: true, reject_if: ->(attrs) { attrs['name'].blank? }
end
option.rb
class Option < ApplicationRecord
belongs_to :accommodation_category
end
accommodation_categories_controller.rb
class AccommodationCategoriesController < ApplicationController
[]
def new_options
@accommodation_category = AccommodationCategory.find(params[:id])
@park = @accommodation_category.park
authorize @accommodation_category
end
def update
@park = Park.find(params[:park_id])
@accommodation_category =
@park.accommodation_categories.find(params[:id])
authorize @accommodation_category
@accommodation_category =
@accommodation_category.update_attributes(accommodation_category_params)
redirect_to root_path
end
private
def accommodation_category_params
params.require(:accommodation_category).permit(:name, :description, options_attributes: [:name, :description, :rank, _destroy])
end
end
new_options.html.erb
<%= render 'options_new_form', park: @park%>
_option_new.html.erb
<%= simple_form_for [@park, @accommodation_category] do |f|%>
<% f.fields_for :options do |option| %>
<%= render 'option_fields', f: option %>
<% end %>
<div>
<%= link_to_add_association 'add option', f, :options %>
</div>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
_option_fields.html.erb
<div>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_field :description %>
<%= f.check_box :_destroy %>
<%= link_to_remove_association "remove option", f %>
</div>
Upvotes: 1
Views: 329
Reputation: 489
I have found the error and @nathanvda was spot on, the params were not correct causing a rollback. I though it might be useful for others to describe how I found out for nested tables, using the cocoon gem.
1) I went to my console and tried creating a new accommodation_category with options, which caused the rollback.
cat11=AccommodationCategory.create(name:'hello', options_attributes:[{name:'option 1'}])
2) Then I checked if it was valid (obviously not)
cat11.valid?
3) Lastly, I displayed the error messages
cat11.errors.full_messages
=> ["Park must exist", "Options age table must exist", "Options price must exist"]
I'm pretty sure I did something wrong with the debugging @nathanvda mentioned, which should have given me the same error message. I fixed the errors by linking the price & age_table (which I should have posted in my question in the first place, but didn't because I deemed it not relevant).
Upvotes: 0
Reputation: 50057
So you have a failing validation, causing the update_attributes
to rollback. Your (shown) controller-code does not handle any errors at all, I would expect something like:
@accommodation_category.update_attributes(accommodation_category_params)
if @accomodation.valid?
redirect_to root_path
else
render :edit
end
Also, when rendering the form, it might be useful to show the validation errors. Simple-form will do this automatically for you (if the errors match to shown fields). Otherwise you could do something like
= simple_form_for [@park, @accommodation_category] do |f|
- if !@accomodation_category.valid?
.alert.alert-error
Failed to save! Error:
= @accomodation_category.errors.full_messages.join(", ")
(I use haml for brevity and clarity)
Upvotes: 1
Reputation: 1168
Try adding inverse_of
class AccommodationCategory < ApplicationRecord
belongs_to :park
has_many :options, dependent: :destroy, inverse_of: :accommodation_category
accepts_nested_attributes_for :options, allow_destroy: true, reject_if: ->(attrs) { attrs['name'].blank? }
end
Upvotes: 1