Search code examples
ruby-on-railsrubynested-attributeslink-tococoon-gem

Rails Cocoon gem not working


Trying to use to Cocoon gem to create nested forms for a recipe app, but doesn't allow me to save new recipes although I've added the ingredient name and recipe steps.

I keep getting the following errors in my localhost:

2 Prevented this recipe from saving

  • Ingredients recipe must exist
  • Directions recipe must exist

I followed a tutorial, have read the Cocoon documentation and research about this issue on Google and SO, but still can't find it. I've been trying to solve this for a few days. Would really appreciate some help please.

Model

class Recipe < ActiveRecord::Base
belongs_to :user

has_many :ingredients
has_many :directions

accepts_nested_attributes_for :ingredients,
                                                        reject_if: proc { |attributes| attributes['name'].blank? },
                                                        allow_destroy: true
accepts_nested_attributes_for :directions,
                                                        reject_if: proc { |attributes| attributes['step'].blank? },
                                                        allow_destroy: true

validates :title, :description, :image, presence: true

has_attached_file :image, styles: { :medium => "400x400#" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/

end

views/recipes/_form

<%= simple_form_for @recipe do |f| %>
<% if @recipe.errors.any? %>
    <div id="error">
        <p><%= @recipe.errors.count %> Prevented this recipe from saving</p>
        <ul>
            <% @recipe.errors.full_messages.each do |message| %>
            <li><%= message %></li>
            <% end %>
        </ul>
    </div>
<% end %>

<div class="panel-body">
    <%= f.input :title %>
    <%= f.input :description %>
    <%= f.input :image %>       
</div>

<div class="row">
    <div class="col-md-6">
        <h3>Ingredients</h3>
        <div id="ingredients">
            <%= f.simple_fields_for :ingredients do |ingredient| %>
                <%= render 'ingredient_fields', f: ingredient %>
            <% end %>
            <div class="links">
                <%= link_to_add_association 'Add Ingredient', f, :ingredients, partial: 'ingredient_fields', class: "btn btn-default add-button" %>
            </div>
        </div>
    </div>
    <div class="col-md-6">
        <h3>Directions</h3>
        <div id="directions">
            <%= f.simple_fields_for :directions do |direction| %>
                <%= render 'direction_fields', f: direction %>
            <% end %>
            <div class="links">
                <%= link_to_add_association 'Add Step', f, :directions, partial: 'direction_fields', class: "btn btn-default add-button" %>
            </div>
        </div>
    </div>
</div>

<div>
    <%= f.button :submit, class: "btn btn-primary" %>
</div>

views/recipes/_ingredient_fields

<div class="form-inline clearfix">
    <div class="nested-fields">
        <%= f.input :name, class: "form-control" %>
        <%= link_to_remove_association 'Remove', f, class: "btn btn-default" %>
    </div>
</div>

views/recipes/_direction_fields

<div class="form-inline clearfix">
    <div class="nested-fields">
        <%= f.input :step, class: "form-control" %>
        <%= link_to_remove_association 'Remove Step', f, class: "btn btn-default" %>
    </div>
</div>

controller

class RecipesController < ApplicationController
before_action :find_recipe, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]

def index
    @recipe = Recipe.all.order("created_at DESC")
end

def show
end

def new
    @recipe = current_user.recipes.build
end

def create
    @recipe = current_user.recipes.build(recipe_params)

    if @recipe.save
        redirect_to @recipe, notice: "Successfully created new recipe"
    else
        render 'new'
    end
end

def edit
end

def update
    if @recipe.update(recipe_params)
        redirect_to @recipe
    else
        render 'edit'
    end
end

def destroy
    @recipe.destroy
    redirect_to root_path, notice: "Successfully deleted recipe"
end

private

    def recipe_params
        params.require(:recipe).permit(:title, :description, :image, ingredients_attributes: [:id, :name, :_destroy], directions_attributes: [:id, :step, :_destroy])
    end

    def find_recipe
        @recipe = Recipe.find(params[:id])
    end
end

Here is a link to the repository if it helps debugging: https://github.com/rchrdchn/rails-projects/tree/master/recipe_box


Solution

  • When you're creating a new recipe, you don't have the recipe object, because it's in the server memory. But when you're updating it, the recipe object is persisted.

    That's why you're getting the Ingredients recipe must exist and directions recipe must exist error.

    To fix that you have to add the inverse_of in the associations.

    class Recipe
      has_many :ingredients, inverse_of: :recipe
      has_many :directions, inverse_of: :recipe
    
    class Ingredient
      belongs_to :recipe, inverse_of: :ingredients
    
    class Direction
      belongs_to :recipe, inverse_of: :directions