Search code examples
javascriptjqueryruby-on-railsrubyapache-cocoon

Does the cocoon gem require n models to be built in the controller new action?


I have installed the cocoon gem in my rails 4 app exactly as described. This works fantastic in the parent model form allowing the user to add/remove fields for the child model(s). Where I'm having trouble is the submission of the child object. If child models are built in the parent model's new action, then I am able to submit exactly however many models were created, no more. This is obvious from the parameters being submitted as they contain child_attributes (or not, if no child models were built in the controller).

Currently running

rails 4.2.10

ruby 2.5.1

cocoon 1.2.14

jquery-rails 4.3.3

jquery-ui-rails 6.0.1

CODE SNIPPETS

class EventsController < ApplicationController
  before_action :authenticate_user!, only: [:new, :create]

  def new
    @event = Event.new
    @event.competitions.build
  end

  def create
    @event = current_user.events.create(event_params)
    if @event.valid?
      flash[:notice] = "Event created"
      redirect_to events_path
    else
      flash[:alert] = "Event not created.  Please check for errors in the form and try again."
      render :new, status: :unprocessable_entity
    end
  end

def event_params
    params.require(:event).permit(
                                  :event_name,
                                  :event_start,
                                  :event_end,
                                  :event_address,
                                  competitions_attributes: [:id, :competition_name, :maximum_participants, :type_id, :fee, :_destroy]
    )
  end
end

Parent model

  acts_as_paranoid
  has_and_belongs_to_many :users
  has_many :competitions, dependent: :destroy, inverse_of: :event
  belongs_to :address
  accepts_nested_attributes_for :address
  accepts_nested_attributes_for :competitions, allow_destroy: true

Child model

class Competition < ActiveRecord::Base
  acts_as_paranoid
  belongs_to :event
  has_many :participants, dependent: :destroy
  belongs_to :type
  accepts_nested_attributes_for :participants, allow_destroy: true  

Form (new.html.erb)

  <div class="text-left ">
    <%= simple_form_for @event do |f| %>
        <%= f.input :event_name, input_html: {maxlength: 60} %>
        <%= f.input :logo, label: "Event Logo:", hint: 'jpg or png files allowed, max size: 1MB' %>
        <a <%= f.input :description, label_html: {class: "glyphicon glyphicon-question-sign event-new", href: "#", 'data-content': "You can format your description using the editor buttons. Cutting and pasting from other text editors will not work unless they are first exported into html format. For security reasons, some html tags are not allowed and will be removed.", rel: "popover", "data-placement": 'top', 'data-original-title': 'WYSIWYG editor help', 'data-trigger': 'hover' }, as: :ckeditor, input_html: { ckeditor: { toolbar: 'mini' } } %></a>
        <%= f.input :event_address, placeholder: "Enter Street Address, City, State, Postal Code" %>
        <%= f.input :registration_fee, :input_html => { :value => '0.00'}, label: "Team registration fee" %>


        <%= f.input :event_start %>
        <%= f.input :event_end %>
        <br />
        <h3>Competitions</h3>
        <div id="competitions">
          <%= f.simple_fields_for :competitions do |competition| %>
            <%= render 'competition_fields', f: competition %>
          <% end %>
          <div class="links">
            <%= link_to_add_association 'add competition', f, :competitions %>
          </div>
        </div>

        <%= f.submit 'Create', :class => 'pull-right btn btn-primary' %>
    <% end %>
  </div

Partial (named _competition_fields.html.erb

<div class="nested-fields">
  <%= f.input :competition_name %>
  <%= f.collection_select(:type_id, @types, :id, :name, prompt: "Select a Type") %>
  <%= f.input :fee, :input_html => { :value => '0.00'} %>
  <%= f.input :maximum_participants %>
  <%= link_to_remove_association "Delete Competition", f %>
</div>

application.js

//= require jquery
//= require bootstrap-sprockets
//= require jquery_ujs
//= require dataTables/jquery.dataTables
//= require jquery-ui/widgets/autocomplete
//= require autocomplete-rails
//= require moment
//= require bootstrap-datetimepicker
//= require ckeditor/init
//= require google_analytics
//= require cocoon
//= require_tree .

Image of the partial component in the form, note 2 competitions submitted

From the Rails console (after the parent model is inserted)

"competitions_attributes"=>{"0"=>{"competition_name"=>"test1", "type_id"=>"2", "fee"=>"0.00", "maximum_participants"=>"8", "_destroy"=>"false"}}}, "commit"=>"Create"}

  SQL (17.4ms)  INSERT INTO "competitions" ("competition_name", "maximum_participants", "type_id", "fee", "event_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["competition_name", "test1"], ["maximum_participants", 8], ["type_id", 2], ["fee", "0.0"], ["event_id", 305], ["created_at", "2019-08-01 11:13:44.582299"], ["updated_at", "2019-08-01 11:13:44.582299"]]

I've been through all the usual issues with the setup for the gem (accepts_nested_attributes_for, inverse_of, the naming and indentation of the child_fields partial, jQuery is installed and cocoon being called etc.) It's all to spec so far as I can tell. And it works, as long as the child models are built in the new action.


Solution

  • Argh! What a facepalm moment. Turned out the <a> tag on the ckeditor description field caused all this fuss. Removing the <a> tag allowed cocoon to operate normally.

    So, in answer to the original question: No, the cocoon gem does -not- require even one child model to be built in the new controller action to add them.