I have been trying to build a fairly complex recipe object that should have an indeterminate number of ingredients("macaroni", "cheese", "butter") and directions ("boil noodles", "add cheese").
The recipe form object is built using the Cacoon gem, allowing users to add those nested ingredient/direction values. I have defined the strong_params in the controller, and those parameters are successfully being sent to the create/update RecipesController action; however the controller is still rolling back the submission.
Any clues on what I have forgotten?
note
If I save a recipe without including any nested attributes, the recipe creates successfully, and then I can edit/update the recipe and add nested attributes. So it's only complaining on the create action...
Here's the relevant console output:
Processing by RecipesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"[token removed for brevity]", "recipe"=>{"name"=>"French bread", "cuisine_id"=>"2", "ingredients_attributes"=>{"0"=>{"quantity"=>"500", "measure"=>"grams", "item"=>"flour", "_destroy"=>"false"}, "1"=>{"quantity"=>"250", "measure"=>"ml", "item"=>"water", "_destroy"=>"false"}, "2"=>{"quantity"=>"10", "measure"=>"grams ", "item"=>"yeast", "_destroy"=>"false"}}, "directions_attributes"=>{"0"=>{"direction"=>"Knead dough", "_destroy"=>"false"}, "1"=>{"direction"=>"Let rise 2 hours", "_destroy"=>"false"}, "2"=>{"direction"=>"Bake in 500 degree oven for 30 min.", "_destroy"=>"false"}}}, "button"=>""}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.1ms) begin transaction
Cuisine Load (0.1ms) SELECT "cuisines".* FROM "cuisines" WHERE "cuisines"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
(0.1ms) rollback transaction
class Recipe < ApplicationRecord
belongs_to :user
belongs_to :cuisine
has_many :ingredients, dependent: :destroy
has_many :directions, dependent: :destroy
validates :name, presence: true
accepts_nested_attributes_for :ingredients,
reject_if: :all_blank,
allow_destroy: true
accepts_nested_attributes_for :directions,
reject_if: :all_blank,
allow_destroy: true
end
class RecipesController < ApplicationController
before_action :verify_user
before_action :set_recipe, only: [:show, :edit, :update, :destroy]
[...]
def new
@recipe = current_user.recipes.build
end
def create
@recipe = current_user.recipes.build(recipe_params)
if @recipe.save
redirect_to @recipe, notice: "Recipe added!"
else
flash.now[:alert] = "There was a problem saving the recipe. Please try again."
render :new
end
end
[...]
private
def set_recipe
@recipe = Recipe.find(params[:id])
end
def recipe_params
params.require(:recipe).permit(:name, :cuisine_id,
ingredients_attributes: [:quantity, :measure, :item, :_destroy],
directions_attributes: [:direction, :_destroy])
end
end
<%= form_for recipe, html: { multipart: true } do |f| %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, placeholder: 'Recipe name', class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :cuisine_id %><br>
<%= f.select(:cuisine_id, options_from_collection_for_select(Cuisine::all, :id, :name),
{:prompt => 'Please Choose'}, :class => "form-control") %>
</div>
<div class="col-md-8">
<div class="form-group">
<h3>Ingredients</h3>
<%= f.fields_for :ingredients do |ingredient| %>
<%= render 'ingredient_fields', f: ingredient %>
<% end %>
<div class="links">
<%= link_to_add_association 'Add Ingredient', f, :ingredients, class: "form-button btn btn-default" %>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<h3>Directions</h3>
<%= f.fields_for :directions do |direction| %>
<%= render 'direction_fields', f: direction %>
<% end %>
<div class="links">
<%= link_to_add_association 'Add Direction', f, :directions, class: "form-button btn btn-default" %>
</div>
</div>
</div>
<div class="form-inline clearfix col-md-12">
<%= f.button :submit, class: "form-button btn btn-success" %>
<%= link_to "Back", url_for(:back), class: "form-button btn btn-default" %>
</div>
<% end %>
<div class="form-inline clearfix">
<div class="nested-fields">
<%= f.number_field :quantity, placeholder: 'qnt', class: "form-control" %>
<%= f.text_field :measure, placeholder: 'measure', class: "form-control" %>
<%= f.text_field :item, placeholder: 'ingredient', class: "form-control" %>
<%= link_to_remove_association "Remove", f, class: "form-button btn btn-default" %>
</div>
</div>
<div class="form-inline clearfix">
<div class="nested-fields">
<%= f.text_area :direction, placeholder: 'Direction', class: "form-control" %>
<%= link_to_remove_association "Remove", f, class: "btn btn-default form-button" %>
</div>
</div>
So the problem was a bug with Rails 5, related to the belongs_to_required_by_default
method being set to false. This explains why records can be updated with nested attributes but not created. You can overcome the problem by either disabling the parent object validations entirely or by including inverse_of: :parent_model_name
in your parent_model.rb file.
More info about issue here