如何在 habits.rb 中将 :missed 天数与 :committed 天数整合?
How to integrate :missed days with :committed days in habits.rb?
我们如何将 t.integer :missed
与 t.text :committed
整合,以便
当用户在 :level
中 :missed
3 :committed
天勾选后,他必须重新启动 :level
?
他每检查 :missed
天,就会在 :level
中添加额外的 :committed
天,这样他必须在前进之前补上它?
每个习惯在 "Mastery"
达到之前有 5 个级别!
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
我猜我们必须在这里区分 :missed
和 :missed
,这取决于它所指的级别。
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
完美运行,但现在 :missed
毫无用处。请帮助我添加适当的逻辑以将 :missed
与 :committed
.
集成
非常感谢您的宝贵时间!
更新
@Dimitry_N's answer 并没有达到这个问题的 1) 或 2),就像我试图让它工作一样。也许你会更幸运地融入他的逻辑。通过他的回答,我也得到了这个错误:How to fix level.rb to work with :committed days?
我觉得程序设计要稍微重新评价一下。我相信 levels
和 days
应该是单独的模型,具有像 level
和 missed
这样的列(遵循 SRP 作为@的概念dgilperez 在他的评论中提到)。因此,我们最终得到四个模型:User
、Habit
、Level
和 Day
,具有以下关联:
- 用户:
has_many :habits
, has_many :levels
- 习惯:
belongs_to:user
、has_many :levels
和has_many :days, through: :levels #for being able to access Habit.find(*).days
- 级别:
belongs_to :user
、belongs_to :habit
和has_many :days
- 日:
belongs_to :level
, belongs_to :habit
通过这些关联,您可以创建具有嵌套属性 的表单。有一个awesome RailCast explaining nested forms。
<%= form_for @habit do |habit| %>
<% 5.times.each_with_index do |number, index| %>
<h1>Level <%= index + 1 %></h1>
<%= habit.fields_for :levels do |level| %>
<%= level.fields_for :days do |day| %>
<%= day.label :missed %>
<%= day.check_box :missed %> <br/>
<% end %>
<% end %>
<% end %>
<%= habit.submit "submit" %>
<% end %>
"magic" 出现在 habits_controller
中,看起来像这样:
class HabitsController < ApplicationController
...
def new
@habit = @user.habits.new
@level = @habit.levels.new
3.times { @level.days.build }
end
def create
@habit = @user.habits.new(habit_params)
@levels = @habit.levels
if @habit.save
@habit.evaluate(@user)
redirect_to ...
else
...
end
end
...
private
def habit_params
params.require(:habit).permit(
:user_id,
levels_attributes:[
:passed,
days_attributes:[
:missed,:level_id]])
end
...
end
请注意 nested strong params
、@habit.evalulate(@user)
方法(我将在下面展示)和 3.times { @level.days.build }
调用,它在您的视图中为嵌套表单构建字段。
habit.evauate(用户)方法:
在保存新的 Habit
后调用此方法。评估属性并将错过的天数和级别的 ID 分别附加到用户的 missed_days
和 missed_levels
属性。逻辑有点笨拙,因为您会将一个数组附加到另一个数组,因此您可能会想出更有效的方法。同时:
def evaluate(user)
levels.each { |level| level.evaluate }
user.missed_levels << levels.where(passed: false).ids
user.missed_days << days.where(missed: true).ids
user.save
end
注意对 level.evaluate
的调用,它看起来像这样:
def evaluate
if days.where(missed: true ).count == 3
update_attributes(passed: false)
else
update_attributes(passed: true)
end
end
架构如下所示:
create_table "days", force: true do |t|
t.integer "level_id"
t.integer "habit_id"
t.boolean "missed", default: false
end
create_table "habits", force: true do |t|
...
t.integer "user_id"
...
end
create_table "levels", force: true do |t|
t.integer "user_id"
t.integer "habit_id"
t.boolean "passed", default: false
end
create_table "users", force: true do |t|
...
t.string "name"
t.text "missed_days" #serialize to Array #serialize to Array in model
t.text "missed_levels" #serialize to Array in model
...
end
并且不要忘记对 Habit 模型使用 accepts_nested_attributes_for :levels, :days
,并且 accepts_nested_attributes_for :days
User。 Here is a git with all my code. 告诉我。
你应该把问题打断,因为在一个问题中有很多要求。 Dimitry_N 似乎走在了正确的轨道上,但您现在需要将一些逻辑添加到关卡模型中。如果您想详细了解,请与我聊天。
我们如何将 t.integer :missed
与 t.text :committed
整合,以便
当用户在
:level
中:missed
3:committed
天勾选后,他必须重新启动:level
?他每检查
:missed
天,就会在:level
中添加额外的:committed
天,这样他必须在前进之前补上它?
每个习惯在 "Mastery"
达到之前有 5 个级别!
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
我猜我们必须在这里区分 :missed
和 :missed
,这取决于它所指的级别。
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
完美运行,但现在 :missed
毫无用处。请帮助我添加适当的逻辑以将 :missed
与 :committed
.
非常感谢您的宝贵时间!
更新
@Dimitry_N's answer 并没有达到这个问题的 1) 或 2),就像我试图让它工作一样。也许你会更幸运地融入他的逻辑。通过他的回答,我也得到了这个错误:How to fix level.rb to work with :committed days?
我觉得程序设计要稍微重新评价一下。我相信 levels
和 days
应该是单独的模型,具有像 level
和 missed
这样的列(遵循 SRP 作为@的概念dgilperez 在他的评论中提到)。因此,我们最终得到四个模型:User
、Habit
、Level
和 Day
,具有以下关联:
- 用户:
has_many :habits
,has_many :levels
- 习惯:
belongs_to:user
、has_many :levels
和has_many :days, through: :levels #for being able to access Habit.find(*).days
- 级别:
belongs_to :user
、belongs_to :habit
和has_many :days
- 日:
belongs_to :level
,belongs_to :habit
通过这些关联,您可以创建具有嵌套属性 的表单。有一个awesome RailCast explaining nested forms。
<%= form_for @habit do |habit| %>
<% 5.times.each_with_index do |number, index| %>
<h1>Level <%= index + 1 %></h1>
<%= habit.fields_for :levels do |level| %>
<%= level.fields_for :days do |day| %>
<%= day.label :missed %>
<%= day.check_box :missed %> <br/>
<% end %>
<% end %>
<% end %>
<%= habit.submit "submit" %>
<% end %>
"magic" 出现在 habits_controller
中,看起来像这样:
class HabitsController < ApplicationController
...
def new
@habit = @user.habits.new
@level = @habit.levels.new
3.times { @level.days.build }
end
def create
@habit = @user.habits.new(habit_params)
@levels = @habit.levels
if @habit.save
@habit.evaluate(@user)
redirect_to ...
else
...
end
end
...
private
def habit_params
params.require(:habit).permit(
:user_id,
levels_attributes:[
:passed,
days_attributes:[
:missed,:level_id]])
end
...
end
请注意 nested strong params
、@habit.evalulate(@user)
方法(我将在下面展示)和 3.times { @level.days.build }
调用,它在您的视图中为嵌套表单构建字段。
habit.evauate(用户)方法:
在保存新的 Habit
后调用此方法。评估属性并将错过的天数和级别的 ID 分别附加到用户的 missed_days
和 missed_levels
属性。逻辑有点笨拙,因为您会将一个数组附加到另一个数组,因此您可能会想出更有效的方法。同时:
def evaluate(user)
levels.each { |level| level.evaluate }
user.missed_levels << levels.where(passed: false).ids
user.missed_days << days.where(missed: true).ids
user.save
end
注意对 level.evaluate
的调用,它看起来像这样:
def evaluate
if days.where(missed: true ).count == 3
update_attributes(passed: false)
else
update_attributes(passed: true)
end
end
架构如下所示:
create_table "days", force: true do |t|
t.integer "level_id"
t.integer "habit_id"
t.boolean "missed", default: false
end
create_table "habits", force: true do |t|
...
t.integer "user_id"
...
end
create_table "levels", force: true do |t|
t.integer "user_id"
t.integer "habit_id"
t.boolean "passed", default: false
end
create_table "users", force: true do |t|
...
t.string "name"
t.text "missed_days" #serialize to Array #serialize to Array in model
t.text "missed_levels" #serialize to Array in model
...
end
并且不要忘记对 Habit 模型使用 accepts_nested_attributes_for :levels, :days
,并且 accepts_nested_attributes_for :days
User。 Here is a git with all my code. 告诉我。
你应该把问题打断,因为在一个问题中有很多要求。 Dimitry_N 似乎走在了正确的轨道上,但您现在需要将一些逻辑添加到关卡模型中。如果您想详细了解,请与我聊天。