Oscar Courchaine
Oscar Courchaine

Reputation: 346

Nested fields made with cocoon are not saving

I have 3 objects, Users, Recipes, and Tasks. Tasks are nested inside Recipes, and Recipes are nested inside Users. I am able to save/add/delete Recipes just fine, and I can add Tasks in the HTML form, but when I go to save, the Tasks do not show up as part of a Recipe, even when I go back to the form. I have been working on this for a while and would appreciate any insight.

Users Controller:

    class UsersController < ApplicationController
    before_filter :authenticate_user_or_admin_or_conduit!
    before_action :set_user, only: [:show, :edit, :update, :destroy]

  respond_to :html

  def index
    @users = User.all
    respond_with(@users)
  end

  def show
    respond_with(@user)
  end

  def new
    @user = User.new
    respond_with(@user)
  end

  def edit
  end

  def create
    @user = User.new(user_params)
    if @user.save
      if conduit_signed_in?
       redirect_to '/conduits', notice: 'User created successfully.'
     elsif admin_signed_in?
      redirect_to '/admins', notice: 'User created successfully.'
    else
      redirect_to @user, notice: 'User created successfully.'
    end
  else
   render :new
 end 
end

def update
  @user.update(user_params)
  respond_with(@user)
end

def destroy
  @user.destroy
  respond_with(@user)
end

private
def set_user
  @user = User.find(params[:id])
end

def user_params
  params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end

Recipes Controller:

class RecipesController < ApplicationController
  before_action :set_recipe, only: [:show, :edit, :update, :destroy]

  # GET /recipes
  # GET /recipes.json
  def index
    @recipes = Recipe.all
  end

  # GET /recipes/1
  # GET /recipes/1.json
  def show
  end

  # GET /recipes/new
  def new
    @recipe = Recipe.new
  end

  # GET /recipes/1/edit
  def edit
  end

  # POST /recipes
  # POST /recipes.json
  def create
    @recipe = Recipe.new(recipe_params)

    respond_to do |format|
      if @recipe.save
        format.html { redirect_to @recipe, notice: 'Recipe was successfully created.' }
        format.json { render :show, status: :created, location: @recipe }
      else
        format.html { render :new }
        format.json { render json: @recipe.errors, status: :unprocessable_entity }
      end
    end
    class UsersController < ApplicationController
    before_filter :authenticate_user_or_admin_or_conduit!
    before_action :set_user, only: [:show, :edit, :update, :destroy]

  respond_to :html

  def index
    @users = User.all
    respond_with(@users)
  end

  def show
    respond_with(@user)
  end

  def new
    @user = User.new
    respond_with(@user)
  end

  def edit
  end

  def create
    @user = User.new(user_params)
    if @user.save
      if conduit_signed_in?
       redirect_to '/conduits', notice: 'User created successfully.'
     elsif admin_signed_in?
      redirect_to '/admins', notice: 'User created successfully.'
    else
      redirect_to @user, notice: 'User created successfully.'
    end
  else
   render :new
 end 
end

def update
  @user.update(user_params)
  respond_with(@user)
end

def destroy
  @user.destroy
  respond_with(@user)
end

private
def set_user
  @user = User.find(params[:id])
end

def user_params
  params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end

Recipes Controller:

class RecipesController < ApplicationController
  before_action :set_recipe, only: [:show, :edit, :update, :destroy]

  # GET /recipes
  # GET /recipes.json
  def index
    @recipes = Recipe.all
  end

  # GET /recipes/1
  # GET /recipes/1.json
  def show
  end

  # GET /recipes/new
  def new
    @recipe = Recipe.new
  end

  # GET /recipes/1/edit
  def edit
  end

  # POST /recipes
  # POST /recipes.json
  def create
    @recipe = Recipe.new(recipe_params)

  end

  # PATCH/PUT /recipes/1
  # PATCH/PUT /recipes/1.json
  def update
    respond_to do |format|
      if @recipe.update(recipe_params)
        format.html { redirect_to @recipe, notice: 'Recipe was successfully updated.' }
        format.json { render :show, status: :ok, location: @recipe }
      else
        format.html { render :edit }
        format.json { render json: @recipe.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /recipes/1
  # DELETE /recipes/1.json
  def destroy
    @recipe.destroy
    respond_to do |format|
      format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_recipe
      @recipe = Recipe.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def recipe_params
      params.require(:recipe).permit(:reward, task_attributes: [:description, :counter, :done, :_destroy, :id])
    end
end

Tasks Controller:

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  # GET /tasks
  # GET /tasks.json
  def index
    @tasks = Task.all
  end

  # GET /tasks/1
  # GET /tasks/1.json
  def show
  end

  # GET /tasks/new
  def new
    @task = Task.new
  end

  # GET /tasks/1/edit
  def edit
  end

  # POST /tasks
  # POST /tasks.json
  def create
    @task = Task.new(task_params)

    respond_to do |format|
      if @task.save
        format.html { redirect_to @task, notice: 'Task was successfully created.' }
        format.json { render :show, status: :created, location: @task }
      else
        format.html { render :new }
        format.json { render json: @task.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /tasks/1
  # PATCH/PUT /tasks/1.json
  def update
    respond_to do |format|
      if @task.update(task_params)
        format.html { redirect_to @task, notice: 'Task was successfully updated.' }
        format.json { render :show, status: :ok, location: @task }
      else
        format.html { render :edit }
        format.json { render json: @task.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /tasks/1
  # DELETE /tasks/1.json
  def destroy
    @task.destroy
    respond_to do |format|
      format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_task
      @task = Task.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def task_params
      params.require(:task).permit(:description, :counter, :done, :notes)
    end
end

Users Model:

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  has_many :characters
end

Recipes Model:

class Recipe < ActiveRecord::Base
    belongs_to :character
    has_many :task, :dependent => :destroy
    accepts_nested_attributes_for :task, allow_destroy: true
end

Tasks Model:

class Task < ActiveRecord::Base
    belongs_to :recipe
end

User Form:

<div id="myform">
  <%= form_for(@character) do |f| %>
  <% if @character.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@character.errors.count, "error") %> prohibited this character from being saved:</h2>

    <ul>
      <% @character.errors.full_messages.each do |message| %>
      <li><%= message %></li>
      <% end %>
    </ul>
  </div>
  <% end %>
<li class="accordion-navigation">
  <a href="#panel6a">Recipes</a>
  <div id="panel6a" class="content">
   <fieldset>
    <legend>Recipes consist of a variety of tasks</legend>
    <div>

      <%= f.fields_for :recipe do |recipe| %>
      <%= render "recipe_fields", :f => recipe %>
      <% end %>
      <div class="links">
        <%= link_to_add_association "Add Recipe", f, :recipe, :class =>"button" %>
      </div>

    </div>
  </fieldset> 
</div>
</li>
<% end %>
</div>

Recipe Form:

<div class="nested-fields">
  <div class="row">  
    <div class="row">
      <div class="large-3 columns">
        <div class="field">          
          <%= f.text_field :reward %>
        </div>
      </div>
      <div class="large-9 columns"> 

        <fieldset>
          <legend>Add Task</legend>
          <div>

            <%= f.fields_for :task do |task| %>
            <%= render "task_fields", :f => task %>
            <% end %>
            <div class="links">
              <%= link_to_add_association "Add Task", f, :task, :class =>"small button" %>
            </div>

          </div>
        </fieldset>


        <span style="float:right"><%= link_to_remove_association "Remove Recipe", f, data: {confirm: "Are you sure?"}, :class =>"button alert" %></span>
      </div>
    </div>
  </div>
</div>

Task Form:

<div class="nested-fields">
    <div class="row">
        <div class="large-3 columns">
            <div class="field">

                <%= f.text_field :description %>
            </div>
        </div>

        <div class="large-3 columns">
            <div class="field">
                <%= f.number_field :counter, label: "Record number if needed" %>
            </div>
        </div>

        <div class="large-3 columns">
            <div class="field">
                <%= f.check_box :done, label: "Completed?" %>
            </div>
        </div>



        <span style="float:right"><%= link_to_remove_association "Remove Task", f, data: {confirm: "Are you sure?"}, :class =>"small button alert" %></span>
    </div>
</div>

Upvotes: 0

Views: 351

Answers (2)

Oscar Courchaine
Oscar Courchaine

Reputation: 346

The other post definitely pointed out an error, but I could not fix it until I added the task_attributes to the recipe_attributes in the User controller, like so:

  params.require(:user).permit(:name, recipe_attributes: [:reward, :_destroy, :user_id, :id, task_attributes: [:description, :recipe_id, :counter, :done, :_destroy, :id]])

Upvotes: 1

Colto
Colto

Reputation: 622

I dealt with this funkiness recently. I'm guessing the Task is being created, just not relating to the Recipe. If that is the case, you need to include the recipe_id in the task_attributes array.

Nesting is all sorts of magic, but for whatever reason, the id of the parent object won't be set unless it's explicitly included as a permitted param.

Hope this helps!

Upvotes: 1

Related Questions