How to integrate :missed days with :committed days in habits.rb?

早过忘川 提交于 2019-12-07 09:09:29

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.

I think the program design has to be slightly re-evaluated. I believe that levels and days should be separate models with columns like level and missed (following the concepts of SRP as @dgilperez mentioned in his comment). Thus, we end up with four models: User, Habit, Level and Day, with the following associations:

  • User: has_many :habits, has_many :levels
  • Habit: belongs_to:user, has_many :levels and has_many :days, through: :levels #for being able to access Habit.find(*).days
  • Level: belongs_to :user, belongs_to :habit and has_many :days
  • Day: 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.

<%= 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 %>

And the "magic" happens in the habits_controller, which looks like this:

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

Note the nested strong params, the @habit.evalulate(@user) method, which I'll show below, and the 3.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's missed_days and missed_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:

  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

note the call to level.evaluate, which looks like this:

  def evaluate
    if days.where(missed: true ).count == 3
      update_attributes(passed: false)
    else
      update_attributes(passed: true)
    end
  end

The schema would look like this:

  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

And don't forget to use accepts_nested_attributes_for :levels, :days for the Habit model, and accepts_nested_attributes_for :days User. Here is a git with all my code. Let me know.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!