1

I was thinking that when a user checked a checkbox, the page would automatically trigger an AJAX call and update the Level model (calling either the create or destroy action). The user wouldn't have to submit the form manually.

class Level < ActiveRecord::Base
  belongs_to :habit
end

The point is that if a user misses a day of doing their good habit they should check off the missed box. For every day that they miss they must make up for it before advancing to the next level in the positive habit formation process.

class Habit < ActiveRecord::Base
    belongs_to :user
    has_many :comments, as: :commentable
    has_many :levels
    serialize :committed, Array
    validates :date_started, presence: true
    before_save :current_level
    acts_as_taggable
    scope :private_submit, -> { where(private_submit: true) }
    scope :public_submit, -> { where(private_submit: false) }

    attr_accessor :missed_one, :missed_two, :missed_three

    def save_with_current_level
        self.levels.build
        self.levels.build
        self.levels.build
        self.levels.build
        self.levels.build
        self.save
    end

    def self.committed_for_today
    today_name = Date::DAYNAMES[Date.today.wday].downcase
    ids = all.select { |h| h.committed.include? today_name }.map(&:id)
    where(id: ids)
  end 

    def current_level
            return 0 unless date_started
            committed_wdays = committed.map { |day| Date::DAYNAMES.index(day.titleize) }
            n_days = ((date_started.to_date)..Date.today).count { |date| committed_wdays.include? date.wday }
            actual_days = n_days - self.missed_days

      case n_days     
          when 0..9
            1
          when 10..24
            2
          when 25..44
            3
          when 45..69
            4
          when 70..99
            5
          else
            "Mastery"
        end
    end
end

You can see the +1 increment here for missing a day. How can we save this via AJAX?

class DaysMissedController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]

def create
  level = Habit.find(params[:habit_id]).levels.find(params[:level_id])
  level.missed_days = level.missed_days + 1
  level.save
  head :ok # this returns an empty response with a 200 success status code
end

def destroy
  level = Habit.find(params[:habit_id]).levels.find(params[:level_id])
  level.missed_days = level.missed_days - 1
  level.save
  head :ok # this returns an empty response with a 200 success status code
end
end

Config file

resources :habits do
  resources :comments
  resources :levels do
    # we'll use this route to increment and decrement the missed days
    resources :days_missed, only: [:create, :destroy]
  end
end

Here is a visual from the habits _form:

enter image description here

habits _form

  <label> Missed: </label>
  <% @habit.levels.each_with_index do |level, index| %>
    <p>
      <label> Level <%= index + 1 %>: </label>
      <%= f.check_box :missed_one, checked: (level.missed_days > 0) %>
      <%= f.check_box :missed_two, checked: (level.missed_days > 1) %>
      <%= f.check_box :missed_three, checked: (level.missed_days > 2) %>
    </p>
  <% end %>

Thank you for your expertise!

tereško
  • 58,060
  • 25
  • 98
  • 150
AnthonyGalli.com
  • 2,796
  • 5
  • 31
  • 80

1 Answers1

1

Not too difficult. In your habits form:

<label id="<%= @habit.id %>" class="habit-id"> Missed: </label>
  <% @habit.levels.each_with_index do |level, index| %>
    <p>
      <label id="<%= level.id %>" class="level-id"> Level <%= index + 1 %>: </label>
      <%= check_box_tag nil, true, level.missed_days > 0, {class: "habit-check"} %>
      <%= check_box_tag nil, true, level.missed_days > 1, {class: "habit-check"} %>
      <%= check_box_tag nil, true, level.missed_days > 2, {class: "habit-check"} %>
    </p>
  <% end %>

In your javascript file:

$(document).ready(function()
{
  $(".habit-check").change(function()
  {
    habit = $(this).parent().siblings(".habit-id").first().attr("id");
    level = $(this).siblings(".level-id").first().attr("id");
    if($(this).is(":checked"))
    {
       $.ajax(
       {
         url: "/habits/" + habit + "/levels/" + level + "/days_missed",
         method: "POST"
       });
    }
    else
    {
       $.ajax(
       {
         url: "/habits/" + habit + "/levels/" + level + "/days_missed/1",
         method: "DELETE"
       });
    }
  });
});

Your destroy method doesn't seem to need an id, so that's why I pass in 1 for it; just to give it an id.

Ryan K
  • 3,985
  • 4
  • 39
  • 42
  • Thank you so much Ryan for this awesome answer! I added your code. And got a long error. I wasn't sure what Javascript file I should add it to: `application.js`? `days_missed.coffee`? `habits.coffee`? I generated a new `one days_missed.js`? I tried all but I got the same error: – AnthonyGalli.com Apr 16 '15 at 15:38
  • Sorry. It has nothing to do with the `javascript`. Updated the form code. Basically the `checked` attribute needed to be inside the brackets. Also updated the `javascript` code (added document ready) – Ryan K Apr 16 '15 at 16:25
  • I can't get this to work Ryan :/ I put it in habit.js too. When I check the box and then go back to the page the checkbox is gone. This also happens when I click submit. The checkbox disappears, in other words it's not saving. I had this problem before and I thought AJAX could fix the issue. – AnthonyGalli.com Apr 16 '15 at 17:15
  • What are `:missed_one`, `:missed_two`, and `:missed_three`? Because you aren't saving/updating them. You are only relying on `level.missed_days` to determine if they are checked or not. So maybe switch them to `check_box_tag`s instead. Are they needed in the model? – Ryan K Apr 16 '15 at 17:19
  • Changed the `check_box`s to `check_box_tag`s. This should decouple the check boxes from the model, until you click on it and the `ajax` fires. – Ryan K Apr 18 '15 at 00:21
  • Thanks so much Ryan. I haven't responded because I was trying to figure out why the checkmarks aren't saving. With your code updated they still aren't saving, after-all they weren't saving before we introduced your awesome code. I think they are needed in the model. Each check should add 1 more day to a level before that level can be considered completed. – AnthonyGalli.com Apr 18 '15 at 01:16
  • Yeah, it seems like the big thing is the relationship between `:days_missed` and the `:missed_x` fields. By the way, in the line `level.missed_days = level.missed_days + 1`, you are sure that `level.missed_days` is initialized to `0` (or at least some number)? Because if not, then the `ajax` request would return with an error. – Ryan K Apr 18 '15 at 01:28
  • Yea `t.integer "missed_days", default: 0` is in the schema for habits.rb. Do you have any ideas on how we can create that relationship between `:days_missed` and `:missed_x` fields? Someone directed me to `attr_accessor` and I thought that would have been enough. The purpose is so that when a user checks `:missed_three` on each level the level would restart. I haven't figured out how to add that yet. Maybe that's the problem :/ – AnthonyGalli.com Apr 18 '15 at 01:57
  • That's probably not the best idea. Because if someone selected `:missed_three` without selecting the other two, then the level would reset when it shouldn't. It should reset when `level.missed_days >= 3`. Btw, does a person WANT to miss days or is that bad? Haven't really figured that out yet. – Ryan K Apr 18 '15 at 02:13
  • haha missing a day is a bad thing. Okay I removed the `:missed_x` field lines. Now if we are just focusing on saving/updating (I'll leave figuring out `level.missed_days >= 3` for another question) what do you think I should do? Should I also make this a new question or is this something that AJAX is suppose to resolve? Can I provide you with more code Ryan? Thanks again! – AnthonyGalli.com Apr 18 '15 at 17:43
  • So you have a submit button? Where does that form go to? And when the check-boxes "disappear", are they not on the page at all or are they just hidden? Can you see them when you inspect the page? – Ryan K Apr 18 '15 at 18:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/75600/discussion-between-anthonygalli-com-and-ryan-k). – AnthonyGalli.com Apr 18 '15 at 19:42
  • Hey @Ryan K. Hope all is well! I ran into an issue with your code we discussed a while back. I posted the question here if you got time to take a look: http://stackoverflow.com/questions/30961241/how-to-fix-ajax-to-always-fire-when-checking-box :-] – AnthonyGalli.com Jun 21 '15 at 14:38