Search code examples
ruby-on-railsrubyhamlcocoon-gem

Rails cocoon gem producing duplicates


I'm searching for quite some time answer for this but solutions which worked out in other situation not working in my case.

PROBLEM 1: When clicking button "Dodaj skladnik" or "Dodaj krok" I get multiple fields instead of one ( sometimes 2 sometimes 3 sometimes even 40)

I spent so far 3 hours trying to troubleshoot this but I have no idea why it works this way

When I hit ctrl+f5 it works but just one time .... if I try to use form again or reload issue still occurs

I have NO duplicate entries of coccon itself.

   amon@AMUNET /app/amanda $ grep -rnw . -e 'cocoon'
Plik binarny ./tmp/cache/assets/sprockets/v3.0/7e/7e5wAAtpwJITUTZozWduRq866c-VsC2gAZvJOcVZmPo.cache pasuje do wzorca
Plik binarny ./tmp/cache/assets/sprockets/v3.0/0l/0l98NX_hEbCP2xKSXIBRKAyLnEDTcUK6gyw5MyNYbPo.cache pasuje do wzorca
Plik binarny ./tmp/cache/assets/sprockets/v3.0/rt/rtICrPE1OuOezvTCGzwYnqRsSHfhoYmMhu8dQSfP-is.cache pasuje do wzorca
./tmp/cache/assets/sprockets/v3.0/V1/V1yaq6iLPp3CDZSltLooShZ6SFBM5vWEjQhQSmoNWEU.cache:3:I"environment-version:ETTI"environment-paths;TTI"rails-env;TTI"Zprocessors:type=application/javascript&file_type=application/javascript&pipeline=self;TTI"{file-digest:///home/amon/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/cocoon-1.2.10/app/assets/javascripts/cocoon.js;TTF
./app/assets/javascripts/application.js:13://= require cocoon
./Gemfile:37:gem 'cocoon', '~> 1.2', '>= 1.2.10'
./Gemfile.lock:50:    cocoon (1.2.10)
./Gemfile.lock:183:  cocoon (~> 1.2, >= 1.2.10)

Some of code: application.js

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require cocoon
//= require_tree .

recipes_controller.rb

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

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

        def show

        end

        def new
            @recipe = Recipe.new
        end

        def create
            @recipe = Recipe.new(recipe_params)

            if @recipe.save
                redirect_to @recipe, notice: "Przepis zostal dodany"
            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: "Przepis zostal usuniety"
        end


        private

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

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

_form.html.haml

=simple_form_for @recipe, html: {multipart: true} do |f|
- if @recipe.errors.any?
    #errors
        %p
        = @recipe.errors.count
        Prevent this recipe from saving
    %ul
        - @recipe.errors.full_messages.each do |msg|
            %li= msg
.panel-body
    = f.input :tittle, input_html: { class: 'form-control' }
    = f.input :description, input_html: { class: 'form-control' }
    = f.input :image, input_html: { class: 'form-control' }
    .row
        .col-md-6
            %h3 Skladniki
            #ingredients
                = f.simple_fields_for :ingredients do |ingredient|
                    = render 'ingredient_fields', f: ingredient
                .links
                    = link_to_add_association 'Dodaj skladnik', f, :ingredients, class: "btn btn-default add-button"
        .col-md-6
            %h3 Wskazowki
            #directions
                = f.simple_fields_for :directions do |direction|
                    = render 'direction_fields', f: direction
                .links
                    = link_to_add_association 'Dodaj krok', f, :directions, class: "btn btn-default add-button"


    = f.button :submit, class: "btn btn-primary"

application.html.haml

    !!! 5
%html
%head
    %title Amanda project
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true
    = javascript_include_tag 'application', 'data-turbolinks-track' => true
    = csrf_meta_tags

%body
    %nav.navbar.navbar-default
        .container
            .navbar-brand= link_to "Amanda project", root_path
            %ul.nav.navbar-nav.navbar-right
                %li= link_to "Dodaj nowy przepis", new_recipe_path
    .container
        - flash.each do |name, msg|
            = content_tag :div, msg, class: "alert"
    = yield

_ingredient_fields.html.haml

.form-inline.clearfix
.nested-fields
    = f.input :name, input_html: { class: "form-input form-control" }
    = link_to_remove_association "Usun", f, class: "form-button btn btn-default"

recipe.rb (Model)

class Recipe < ApplicationRecord
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 :tittle, :description, :image, presence: true
has_attached_file :image, styles: { :medium => "400x400#" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
end

PROBLEM 2 Also I have problem with saving it. Even when I fill multiple inupts for ingredients and directions I get message

Ingredients recipe must exist

It is huge blocking point for me now so please help.


Solution

  • As explained in the documentation, when using rails 5 you should write your association differently:

    has_many :ingredients, inverse_of: :recipe
    

    In short: since rails 5 a belongs_to relation is by default required. While this absolutely makes sense, this also means associations have to be declared more explicitly. When saving nested items, theoretically the parent is not yet saved on validation, so rails needs help to know the link between relations.

    The first error sounds like a turbolinks error: somehow you include the application.js or cocoon.js multiple times (e.g. if you put it at the bottom of the page --not visible from the code you showed, but this would explain it). Turbolinks will replace the complete body-contents but not the head, and so any javascript in the body will be loaded multiple times (navigating, following links), until you actually refresh the page.