techquestion
techquestion

Reputation: 489

Unable to detect error in saving a nested object

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

Answers (3)

techquestion
techquestion

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

nathanvda
nathanvda

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

Vibol
Vibol

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

Related Questions