How to restart current_level for every third missed_day (3 hits you got out!)?
For example, if the user is at level 4: Day 50 , it goes back to Day 45 .
case n_days
when 0..9 # Would go back to 0
1
when 10..24 # Back to 10
2
when 25..44 # Back to 25
3
when 45..69 # Back to 45
4
when 70..99 # Back to 70
5
else
"Mastery"
end
Then let's say it slips back into place, this time until Day 68 , if he checks 3 missed_days
again, it gets dropped again on Day 45 :
_form (presented above):
<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 %>
habit.rb
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_strike
levels[current_level - 1] # remember arrays indexes start at 0
end
def current_level
return 0 unless date_started
def committed_wdays
committed.map do |day|
Date::DAYNAMES.index(day.titleize)
end
end
def n_days
((date_started.to_date)..Date.today).count do |date|
committed_wdays.include? date.wday
end - self.missed_days
end
case n_days
when 0..9
1
when 10..24
2
when 25..44
3
when 45..69
4
when 70..99
5
else
6
end
end
end
habit.js
$(document).ready(function() {
var handleChange = function() {
habit = $(this).parent().prev().attr("id");
level = $('label', $(this).parent()).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"
});
}
if (!$('input[type="checkbox"]:not(:checked)', $(this).parent()).length) {
/* this is just an example, you will have to ammend this */
$(this).parent().append($('<input type="checkbox" class="habit-check">'));
$(this).parent().append($('<input type="checkbox" class="habit-check">'));
$(this).parent().append($('<input type="checkbox" class="habit-check">'));
$(".habit-check").on('change',handleChange);
}
}
$(".habit-check").on('change',handleChange);
});
days_missed_controller.rb
class DaysMissedController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def create
habit = Habit.find(params[:habit_id])
habit.missed_days = habit.missed_days + 1
habit.save!
level = habit.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
habit = Habit.find(params[:habit_id])
habit.missed_days = habit.missed_days - 1
habit.save
level = habit.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
Here's the Gist : https://gist.github.com/RallyWithGalli/c66dee6dfb9ab5d338c2
Please let me know if you need more code, explanation or pictures :)
source to share
Here is my solution:
You need to keep track of the days you lost at the moment you got 3 missed days (you need to add a field to the level):
migration file .rb
class AddDaysLostToLevels < ActiveRecord::Migration
def change
add_column :levels, :days_lost, :integer, default: 0
end
end
Then change the days_missed controller to reset when you get 3 missed days and keep the days you lost when you start the level again (in the days_lost variable):
class DaysMissedController < ApplicationController
def create
habit = Habit.find(params[:habit_id])
habit.missed_days = habit.missed_days + 1
habit.save!
level = habit.levels.find(params[:level_id])
level.missed_days = level.missed_days + 1
if level.missed_days == 3
level.missed_days = 0
level.days_lost += habit.calculate_days_lost + 1
end
...remain the same
Now we need to define the methods "calculate_days_lost" and "real_missed_days" in the habit.rb file.
Remember to use self.real_missed_days instead of self.missed_days in the current_level: method (they differ from each other because you can start the level again). The new habit.rb will look like this:
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_strike
levels[current_level - 1] # remember arrays indexes start at 0
end
def real_missed_days
value = 0
levels.each do |level|
if level.missed_days != nil
value += level.missed_days + level.days_lost
end
end
value
end
def committed_wdays
committed.map do |day|
Date::DAYNAMES.index(day.titleize)
end
end
def n_days
((date_started.to_date)..Date.today).count do |date|
committed_wdays.include? date.wday
end - self.real_missed_days
end
def calculate_days_lost
case n_days
when 0..9
n_days
when 10..24
n_days-10
when 25..44
n_days-25
when 45..69
n_days-45
when 70..99
n_days-70
else
n_days-100
end
end
def current_level
return 0 unless date_started
case n_days
when 0..9
1
when 10..24
2
when 25..44
3
when 45..69
4
when 70..99
5
else
6
end
end
end
source to share
If I understand correctly what you are trying to achieve here, we expect the user to do something every day for several days. If he doesn't do it 3 times in a given "level period", you want to reset his "progress level", right?
If this is the case, the easiest solution would be to add another variable, for example: days_lost
.
Then you can apply it in habit.rb :
n_days = ((date_started.to_date)..Date.today).count { |date| committed_wdays.include? date.wday } - self.missed_days - self.days_lost
Remember to update it every time the user misses 3 times, which looks like days_missed_controller.rb :
def create
...
if missed_days == 3
missed_days = 0
days_lost = <days from beginning of the level>
end
...
end
UPDATE
<days from beginning of the level>
could be counted by creating another helper variable eg. pending_days
and give it an initial value 0
. Every day pending_days += 1
.
Next we deal:
1: The player "skips" 3 days at his current level - we add days from the start of the level to negative (lost) days and start counting again
days_lost += pending_days pending_days = 0
2: The player "misses" for less than 3 days - we just increase his level and start counting again, because he is at a new level
pending_days = 0
source to share