How can we integrate t.integer :missed
with t.text :committed
so that
when a User checks off he
:missed
3:committed
days in a:level
he has to restart the:level
?for each
:missed
day he checks off, an additional:committed
day is added back into the:level
so that he must make it up before advancing?
Each habit has 5 levels before "Mastery"
is achieved!
class Habit < ActiveRecord::Base
belongs_to :user
before_save :set_level
acts_as_taggable
serialize :committed, Array
def self.comitted_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 levels
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 }
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
private
def set_level
self.level = levels
end
end
I'm guessing we would have to distinguish :missed
from :missed
here depending on what level it is referring to.
habits/_form.html.erb
<label> Missed: </label>
<div>
<label> Level 1: </label>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
</div>
<div>
<label> Level 2: </label>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
</div>
<div>
<label> Level 3: </label>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
</div>
<div>
<label> Level 4: </label>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
</div>
<div>
<label> Level 5: </label>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
<%= f.check_box :missed %>
</div>
habits_controller.rb
class HabitsController < ApplicationController
before_action :set_habit, only: [:show, :edit, :update, :destroy]
before_action :logged_in_user, only: [:create, :destroy]
def index
if params[:tag]
@habits = Habit.tagged_with(params[:tag])
else
@habits = Habit.all.order("date_started DESC")
@habits = current_user.habits
end
end
private
def habit_params
params.require(:habit).permit(:missed, :left, :level, :date_started, :trigger, :target, :positive, :negative, :tag_list, :committed => [])
end
end
_create_habits.rb
class CreateHabits < ActiveRecord::Migration
def change
create_table :habits do |t|
t.integer :missed
t.integer :level
t.text :committed
t.datetime :date_started
t.string :trigger
t.string :target
t.string :positive
t.string :negative
t.references :user, index: true
t.timestamps null: false
end
add_foreign_key :habits, :users
add_index :habits, [:user_id, :created_at]
end
end
:committed
works perfectly, but right now :missed
serves no purpose. Please help me add the appropriate logic to integrate :missed
with :committed
.
Thank you so much for your time!
UPDATE
@Dimitry_N's answer doesn't achieve either 1) or 2) of this question as much as I've tried to make it work. Maybe you'll have a better luck incorporating his logic. With his answer I also get this error: How to fix level.rb to work with :committed days?
I think the program design has to be slightly re-evaluated. I believe that
levels
anddays
should be separate models with columns likelevel
andmissed
(following the concepts of SRP as @dgilperez mentioned in his comment). Thus, we end up with four models:User
,Habit
,Level
andDay
, with the following associations:has_many :habits
,has_many :levels
belongs_to:user
,has_many :levels
andhas_many :days, through: :levels #for being able to access Habit.find(*).days
belongs_to :user
,belongs_to :habit
andhas_many :days
belongs_to :level
,belongs_to :habit
With these associations, you can create a form with nested attributes. There is an awesome RailCast explaining nested forms.
And the "magic" happens in the
habits_controller
, which looks like this:Note the
nested strong params
, the@habit.evalulate(@user)
method, which I'll show below, and the3.times { @level.days.build }
call, which builds the fields for the nested form in your view.habit.evauate(user) method: This method is called after a new
Habit
is saved. Attributes are evaluated and ids of missed days and levels get appended to user'smissed_days
andmissed_levels
attributes respectively. The logic is a bit clunky since you'll be appending one Array to another, so you can probably come up with something more efficient. Meanwhile:note the call to
level.evaluate
, which looks like this:The schema would look like this:
And don't forget to use
accepts_nested_attributes_for :levels, :days
for the Habit model, andaccepts_nested_attributes_for :days
User. Here is a git with all my code. Let me know.You should break the question done because this is a lot to ask for in one question. Dimitry_N seemed to be on the right track, but you'll need to add some of your logic to the levels model now. Please chat with me if you want to go over the details.