5

I have a to do list function in my app which appears to be creating duplicate tasks every time I try to enter one. (You can see the error live at https://www.thestaysanemom.com/tasks with the username 'test@test.com' and password of 'password'.)

The app uses ajax to function, which I can only imagine is causing the error.

The create method is like this:

  def create
    @task = Task.new(task_params)
    @task.user_id = current_user.id
    if @task.save
      respond_to do |format|
        format.js
        format.html
      end
    else
      render :new
    end
  end

Here's the view where it all takes place:

<% if current_user %>

      <div class="container">
            <div class="row">
                <div class="col-xs-12 col-md-6 col-lg-12">
                <div class="content-box to-do">
                  <h2 class="font-script text-center">One-Time Tasks</h2>
            <p class="text-center">These tasks are here to stay, until you complete them.</p>
                        <div id="onetime-todo"><%= render partial: 'items', locals: { task: @one_time } %></div>
                        <div id="onetime-done"><%= render partial: 'done', locals: { task: @one_time_done } %></div>
                </div> <!-- content box -->
                </div> <!-- col -->

                <div class="col-xs-12 col-md-6 col-lg-4">
                <div class="content-box to-do">
                  <h2 class="font-script text-center">Daily</h2>
                        <p class="text-center">These automatically uncheck at night so you have a fresh list&nbsp;in&nbsp;the&nbsp;morning.</p>
                  <div id="daily-todo"><%= render partial: 'items', locals: { task: @daily } %></div>
                        <div id="daily-done"><%= render partial: 'done', locals: { task: @daily_done } %></div>
                </div> <!-- content box -->
                </div> <!-- col -->

                <div class="col-xs-12 col-md-6 col-lg-4">
                <div class="content-box to-do">
                  <h2 class="font-script text-center">Weekly</h2>
                        <p class="text-center">These automatically uncheck Sunday night so you get a new list&nbsp;each&nbsp;Monday&nbsp;morning.</p>
                  <div id="weekly-todo"><%= render partial: 'items', locals: { task: @weekly } %></div>
                        <div id="weekly-done"><%= render partial: 'done', locals: { task: @weekly_done } %></div>
                </div> <!-- content box -->
                </div> <!-- col -->

                <div class="col-xs-12  col-md-6 col-lg-4">
                <div class="content-box to-do">
                  <h2 class="font-script text-center">Monthly</h2>
                        <p class="text-center">These automatically uncheck on the last day of the month so you start with a clean&nbsp;list&nbsp;each&nbsp;1st.</p>
                  <div id="monthly-todo"><%= render partial: 'items', locals: { task: @monthly } %></div>
                        <div id="monthly-done"><%= render partial: 'done', locals: { task: @monthly_done } %></div>
                </div> <!-- content box -->
                </div> <!-- col -->
            </div> <!-- row -->
      </div> <!-- container -->
<% end %> <!-- current_user -->

<script>
    $(document).ready(function() {
        $('.content-box').matchHeight();

    });
</script>

Here's my create.js.erb:

$("#onetime-todo").html("<%= escape_javascript(render partial: 'items', locals: { task: @one_time }) %>")
$("#onetime-done").html("<%= escape_javascript(render partial: 'done', locals: { task: @one_time_done }) %>")

$("#daily-todo").html("<%= escape_javascript(render partial: 'items', locals: { task: @daily }) %>")
$("#daily-done").html("<%= escape_javascript(render partial: 'done', locals: { task: @daily_done }) %>")

$("#weekly-todo").html("<%= escape_javascript(render partial: 'items', locals: { task: @weekly }) %>")
$("#weekly-done").html("<%= escape_javascript(render partial: 'done', locals: { task: @weekly_done }) %>")

$("#monthly-todo").html("<%= escape_javascript(render partial: 'items', locals: { task: @monthly }) %>")
$("#monthly-done").html("<%= escape_javascript(render partial: 'done', locals: { task: @monthly_done }) %>")

$('#textField').val("");

Which renders _items.html.erb:

<div class="to-do-list taskWrapper" data-url="<%= sort_tasks_path %>">
  <% task.each do |task| %>
  <div id="<%= dom_id(task) %>">
    <%= link_to task do %>
      <p>
        <%= fa_icon "bars", class: "color-neutral-light", style: "margin-right: 5px;" %>
        <%= link_to check_task_path(task), method: :post, remote: true do %>
          <%= fa_icon "square-o", style: "margin-right: 5px;" %>
        <% end %>
        <span id="task-show-hide">
          <span class="font-serif">
            <%= task.name %>
          </span>
          <span>
            <%= link_to task_path(task), method: :delete, remote: true do %>
              <%= fa_icon "remove", id: (task.id.to_s + "task"), style: "margin-left: 5px" %>
            <% end %>
          </span>
        </span>
      </p>
    <% end %> <!-- dom id wrapper -->
  </div>
  <% end %> <!-- task each -->
</div>

<script>
    $(document).on('ready page:load', function() {
        $('.content-box').matchHeight();
    $(".taskWrapper").sortable({

      update: function(e, ui) {
        var $that = $(e.target);

        Rails.ajax({
          url: $(this).data("url"),
          type: "PATCH",
          data: $that.sortable('serialize'),
        });

        console.log(ui.item.index())
      }
    });
    });
</script>

And I don't think it matters, but for completion's sake here's the schema for tasks.

 create_table "tasks", force: :cascade do |t|
    t.string "name"
    t.string "frequency"
    t.boolean "completed", default: false
    t.integer "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "position"
    t.index ["user_id"], name: "index_tasks_on_user_id"
  end

Can anyone see why this is creating duplicates?

Additional Info: ERB Form Creation

        <%= simple_form_for(@create_task, remote: true) do |f| %>
            <div class="row padded">
                <div class="col-xs-12 col-sm-9">
                    <%= f.text_field :name, id: "textField", placeholder: "What needs to get done?", class: "form-control" %>
                </div>
                <div class="col-xs-12 col-sm-3">
                    <%= f.select :frequency, options_for_select([["One-Time", "OneTime"],["Daily", "Daily"],["Weekly", "Weekly"],["Monthly", "Monthly"]]), {}, {class: "form-control"} %>
                </div>
                <div class="container text-center">
                    <div class="half-buffer"></div>
                    <%= f.button :submit, class: "btn btn-outline-secondary" %>
                    <div class="half-buffer"></div>
                </div>
            </div> <!-- row -->
        <% end %>

ADDITIONAL INFORMATION

As requested, here are my required statements inside my application.js.erb:

//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require popper
//= require magnific-popup
//= require jquery-ui
//= require froala_editor.min.js
//= require plugins/align.min.js
//= require plugins/char_counter.min.js
//= require plugins/code_beautifier.min.js
//= require plugins/code_view.min.js
//= require plugins/colors.min.js
//= require plugins/font_size.min.js
//= require plugins/fullscreen.min.js
//= require plugins/image.min.js
//= require plugins/image_manager.min.js
//= require plugins/inline_style.min.js
//= require plugins/line_breaker.min.js
//= require plugins/link.min.js
//= require plugins/lists.min.js
//= require plugins/paragraph_format.min.js
//= require plugins/paragraph_style.min.js
//= require plugins/quote.min.js
//= require plugins/special_characters.min.js
//= require plugins/url.min.js

** ADDED GEMFILE **

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.0'

gem 'rails', '~> 5.2.0'
gem 'puma', '~> 3.11'
gem 'puma_worker_killer'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.2'
gem 'jbuilder', '~> 2.5'
gem 'mini_magick'
gem 'jquery-rails'
gem 'devise'
gem 'bootsnap'
gem 'bootstrap', '~> 4.1.3'
gem 'jquery-ui-rails'
gem 'sprockets-rails'
gem 'bootstrap-sass'
gem 'bcrypt', '~> 3.1.7'
gem 'friendly_id', '~> 5.2.0'
gem 'stripe'
gem 'figaro'
gem 'magnific-popup-rails', '~> 1.1.0'
gem 'simple_form'
gem 'acts-as-taggable-on', '~> 6.0' #must be this version for Rails5
gem 'aws-sdk' , '~> 3'
gem 'aws-sdk-s3', require: false
gem 'simple_form_extension'
gem 'recaptcha', require: "recaptcha/rails"
gem 'font-awesome-rails'
gem 'trix-rails', require: 'trix'
gem 'rack-tracker'
gem 'high_voltage', '~> 3.1'
gem 'convertkit-ruby', require: 'convertkit'
gem 'dotenv-rails'
gem 'acts_as_list'
gem 'wysiwyg-rails'
gem 'font-awesome-sass'
gem 'will_paginate'

group :production do
  gem 'pg', '~> 0.20.0'
end

group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'sqlite3'
end

group :development do
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  gem 'binding_of_caller'
  gem 'better_errors'
end

group :test do
  gem 'capybara', '>= 2.15', '< 4.0'
  gem 'selenium-webdriver'
  gem 'chromedriver-helper'
end

gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Liz
  • 1,369
  • 2
  • 26
  • 61

1 Answers1

4

Your rails code looks fine, but your form is getting double-submitted when you submit. You should be able to see this in the Chrome dev tools as below.

screenshot

Both of those references point to the application.js file, but different lines. It's hard to tell which libraries are catching the submit because the file is concatenated and doesn't include the debug information. Without seeing which JS files you're using (from application.js), I'd venture a guess that you might have both rails-ujs & jquery_ujs included which is causing the double submit.

Check the related question and answers here: Form submitted twice, due to :remote=>true

Update: You've definitely got both rails-ujs & jquery_ujs present in your compiled JS file. I think that's a good place to look. If you're on Rails 5 and using the jquery-rails gem you might look here: https://github.com/rails/jquery-rails#installation

If you are running Rails 5.1 and up, and if you have 
included //= require rails-ujs, then jquery_ujs is not needed anymore

You just need to remove the //= require jquery_ujs line from your app/assets/javascripts/appplication.js file

Jay Dorsey
  • 3,563
  • 2
  • 18
  • 24
  • Thanks for the answer! I tried removing `Jquery_ujs` and it got rid of the duplication issue, but now the drag/drop functionality no longer works. Maybe remove `rails-ujs`? – Liz Dec 13 '18 at 22:18
  • With `jquery_ujs` included and `rails-ujs` removed the duplication is still gone, but the drag n drop doesn't work (until you hit refresh, then it does). – Liz Dec 14 '18 at 02:11
  • @Liz what plugin are you using for the drag and drop? I don't see anything listed in your application.js what would do that kind of feature. Can you tell me how to recreate the drag & drop feature as an end user (e.g. is it in the app already, used for sorting, or something else? should i be able to click on a task and sort it?) – Jay Dorsey Dec 14 '18 at 14:14
  • I did it according to this gorails episode (https://gorails.com/episodes/sortable-drag-and-drop) with jquery sortable and jquery-ujs. And its already implemented. You should be able to change the task order within a list by drag and drop. – Liz Dec 14 '18 at 14:54
  • @Liz are you testing this locally, or deployed? If local have you run `rake assets:clobber` to remove the assets (so they get rebuilt) – Jay Dorsey Dec 14 '18 at 23:13
  • I'm testing on the deployed version. But I tried this on local and it didn't change anything. – Liz Dec 15 '18 at 22:39
  • @liz you're missing a dozen (literally) CSS files. They show up as 404. I'm wondering if you changed something else when you made your changes. I don't think a broken CSS should break any javascript, but this is going to be difficult to debug. Can you post your Gemfile as well? And what do you think is supposed to be triggering the sorting? (jQuery, a specific plugin, etc.) – Jay Dorsey Dec 15 '18 at 23:25
  • @liz when you load the page for the first time, if there are tasks, you can order them (e.g. it works). It only breaks once you add a new task. This is trickier to debug, but you need to figure out how the drag and drop plugin works, and figure out how to re-attach everything. It would help to know which plugin you're using, and whether you're using turbolinks. Need to see a gemfile – Jay Dorsey Dec 15 '18 at 23:27
  • I added the gemfile to the OP as requested. Thank you for your help on this. And no, I'm not using Turbolinks. The drag and drop was done via a gorails episode (linked above) with jquery sortable and jquery-ujs. – Liz Dec 16 '18 at 02:26
  • @liz I think I got it figured. In your create.js.erb file you need to re-trigger or call `$(".taskWrapper").sortable` (with all the options/arguments). – Jay Dorsey Dec 16 '18 at 02:55
  • 1
    Eeek! It worked! Thank you so much for the help with this! – Liz Dec 16 '18 at 07:29