Mel Mel - 1 month ago 19
Javascript Question

Rails 5, Cocoon Gem - nested form inside nested form

I am trying to use cocoon gem to build nested forms.

I have models for Organisation, Package::Bip and Tenor.

The associations are:

Organisation

has_many :bips, as: :ipable, class_name: Package::Bip
accepts_nested_attributes_for :bips, reject_if: :all_blank, allow_destroy: true


Package::Bip (polymorphic)

belongs_to :ipable, :polymorphic => true, optional: true, inverse_of: :bip

has_one :tenor, as: :tenor
accepts_nested_attributes_for :tenor, reject_if: :all_blank, allow_destroy: true


Tenor (polymorphic)

belongs_to :tenorable, :polymorphic => true, optional: true


The forms have:

In my organisations/_form.html.erb, I have:

<%= f.simple_fields_for :bips do |f| %>
<%= f.error_notification %>
<%= render 'package/bips/bip_fields', f: f %>

<% end %>

<%= link_to_add_association 'Add another intellectual property resource', f, :bips, partial: 'package/bips/bip_fields' %>


In my bip_fields.html.erb nested form, I have:

<%# if @package_bips.tenor.blank? %>
<%= link_to_add_association 'Add timing', f, :tenor, partial: 'tenors/tenor_fields' %>
<%# end %>

<%= f.simple_fields_for :tenor do |tenor_form| %>
<%= f.error_notification %>
<%= render 'tenors/tenor_fields', f: tenor_form %>
<% end %>


Javascript

The cocoon docs suggest adding a js file to specify association-insertion-node as a function. In my tenor_subform.js I have:

$(document).ready(function() {
$(".add_tenor a").
data("association-insertion-method", 'append').
data("association-insertion-node", function(link){
return link.closest('.row').next('.row').find('.tenor_form')
});
});


Controllers

In my organisation controller, I have:

def new
@organisation = Organisation.new
@organisation.bips
end


Note: I'm not sure if I need to add another line to my new action to create the organisation.bip.tenor instance. I'm also unsure if im supposed to add has_one through association on the organisation.rb that references the tenor.

def organisation_params
params.fetch(:organisation, {}).permit(:title, :comment,

bips_attributes: [:id, :status, :_destroy,
tenor_attributes: [:id,:commencement, :expiry, :_destroy]

],


In my tenor controller, I have:

def tenor_params
params.require(:tenor).permit( :commencement, :expiry)
end


ERRORS

I am not sure if I need to add tenor actions to the organisation controller (the ultimate parent of bip which in turn is the parent of tenor).

When I save all of this and try it, I get an error that says:

unknown attribute 'tenor_id' for Tenor.


When I see other SO posts with this error, its often because the :id attribute hasn't been whitelisted in the parent class. I have done that.

Can anyone see what I've done wrong?

Tenor controller

class TenorsController < ApplicationController

before_action :set_tenor, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# after_action :verify_authorized

def index
@tenors = Tenor.all
# authorize @tenors
end

def show

end

def new
@tenor = Tenor.new
# authorize @tenor
end

def edit

end

def create
@tenor = Tenor.new(tenor_params)
# authorize @tenor

respond_to do |format|
if @tenor.save
format.html { redirect_to @tenor }
format.json { render :show, status: :created, location: @tenor }
else
format.html { render :new }
format.json { render json: @tenor.errors, status: :unprocessable_entity }
end
end
end

def update
respond_to do |format|
if @tenor.update(tenor_params)
format.html { redirect_to @tenor }
format.json { render :show, status: :ok, location: @tenor }
else
format.html { render :edit }
format.json { render json: @tenor.errors, status: :unprocessable_entity }
end
end
end

def destroy
@tenor.destroy
respond_to do |format|
format.html { redirect_to action: :index }
format.json { head :no_content }
end
end

private
def set_tenor
@tenor = Tenor.find(params[:id])
# authorize @tenor
end

def tenor_params
params.require(:tenor).permit(:express_interest, :commencement, :expiry, :enduring, :repeat, :frequency)
end

end

Answer

You has_one relation is wrongly declared. Because you say as: :tenor makes it look for a tenor_id.

You have to declare it as follows:

has_one :tenor, as: :tenorable