How to fix level.rb to work with :committed days?

断了今生、忘了曾经 提交于 2019-12-12 04:56:47

问题


Originally I had :committed days working beautifully, but upon changing up the models a bit in order to accommodate the User's ability to check off if he missed a :committed day I now get an error message for the code relating to :committed:

undefined method to_date for nil:NilClass Line #30 n_days = ((date_started.to_date)..Date.today).count { |date| committed_wdays.include? date.wday }

This code comes from the habit model:

class Habit < ActiveRecord::Base
	belongs_to :user
	has_many :levels
	has_many :days, through: :levels #for being able to access Habit.find(*).days
	accepts_nested_attributes_for :levels, :days
	before_save :set_level
	acts_as_taggable
	serialize :committed, Array

	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

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

The logic behind it all is that a User creates a habit he wants to challenge. To achieve "mastery" in the habit he must complete 5 levels. Each level has a certain amount of :committed days that must be completed before advancing, as broken down above with n_days.

The User commits in the _form to what days (Sun thru Sat) he wants to do the habit. For example he could just choose sun, wed, sat. Then the app should only calculate n_days according to non-:missed :committed days (once 10 of those days passes it moves onto the 2nd level).

class Level < ActiveRecord::Base
  belongs_to :habit
  belongs_to :user
  has_many :days

  accepts_nested_attributes_for :days

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

end

class Day < ActiveRecord::Base
	belongs_to :level
	belongs_to :habit
end

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

  def show
  end

  def new
    @goal = current_user.goals.build
    @habit = current_user.habits.build
    @level = current_user.levels.build
    3.times { @level.days.build }
  end

  def edit
  end

  def create
    @habit = current_user.habits.build(habit_params)
    @levels = @habit.levels
    if  @habit.evaluate(@user)
        redirect_to @habit, notice: 'Habit was successfully created.'
    else
        @feed_items = []
        render 'pages/home'
    end
  end

  def update
    if @habit.update(habit_params)
      redirect_to @habit, notice: 'Habit was successfully updated.'
    else
      render action: 'edit'
    end
  end

  def destroy
    @habit.destroy
    redirect_to habits_url
  end

  private
    def set_habit
      @habit = Habit.find(params[:id])
    end

    def correct_user
      @habit = current_user.habits.find_by(id: params[:id])
      redirect_to habits_path, notice: "Not authorized to edit this habit" if @habit.nil?
    end

  def habit_params
    params.require(:habit).permit(
      :user_id, 
      :level, 
      :left, 
      :date_started, 
      :trigger, 
      :target, 
      :positive, 
      :negative, 
      :tag_list, 
      :committed => [],
      :levels_attributes => [
      :passed,
      :days_attributes => [
      :missed,:level_id]])
  end
end

<%= simple_form_for(@habit) do |f| %>
  <%= f.error_notification %>

<div class="america">
  <form>
    <div class="committed">
      <%= f.label "Committed to:" %>&nbsp;
      <%= f.collection_check_boxes :committed, Date::DAYNAMES, :downcase, :to_s %>
    </div>
    <p>
    <div class="date-group">
    <label> Started: </label>
      <%= f.date_select :date_started, :order => [:month, :day, :year], class: 'date-select' %>
    </div>
    </p>
    <p>
      <%= f.text_field :trigger, class: 'form-control', placeholder: 'Enter Trigger' %></p>
    <p>
      <%= f.text_field :tag_list, habit: @habit.tag_list.to_s.titleize, class: 'form-control', placeholder: 'Enter Action' %>
    </p>
    <p>
      <%= f.text_field :target, class: 'form-control', placeholder: 'Enter Target' %>
    </p>
    <p>
      <%= f.text_field :positive, class: 'form-control', placeholder: 'Enter Reward' %>
    </p>

  <% 5.times.each_with_index do |number, index| %> 
    <h1>Level <%= index + 1 %></h1>
    <%= f.fields_for :levels do |level| %>
      <%= level.fields_for :days do |day| %>
        <%= day.label :missed %>
        <%= day.check_box :missed %> <br/>
      <% end %>
    <% end %>
  <% end %>


<div class="america2">
  <%= button_tag(type: 'submit', class: "btn") do %>
  <span class="glyphicon glyphicon-plus"></span>
  <% end %>

  <%= link_to habits_path, class: 'btn' do %>
  <span class="glyphicon glyphicon-chevron-left"></span>
  <% end %>

  <%= link_to @habit, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn' do %>
  <span class="glyphicon glyphicon-trash"></span>
  <% end %>
</div>
  
</form>
</div>
<% end %>

<!-- Default bootstrap panel contents -->
<div id="valuations" class="panel panel-default">
  
  <div class="panel-heading"><h4><b>HABITS</b></h4></div>

  <!-- Table -->
  <table>
  <thead>
    <tr>
      <th>Level</th>
      <th>Left</th>
      <th>Strike</th>
      <th>Trigger</th>
      <th>Action</th>
      <th>Target</th>
      <th>Reward</th>
      <th>Days</th>
    </tr>
  </thead>

  <tbody>
    <% @habits.each do |challenged| %>
      <tr>
        <td><%= challenged.level %></td>
        <td><%= challenged.left %></td>
        <td>
        <%= link_to edit_habit_path(challenged) do %>
        <%= [params[:missed]].flatten.length %>
        <% end %></td>
        <td><%= challenged.trigger %></td>
        <td class="category">
          <b><%= raw challenged.tag_list.map { |t| link_to t.titleize, taghabits_path(t) }.join(', ') %></b>
        </td>
        <td><%= challenged.target %></td>
        <td><%= challenged.positive %></td>
        <td class= "committed">
          <%= challenged.committed.map { |d| d.titleize[0,3] }.join ', ' %></td>
      </tr>
    <% end %>
  </tbody>
</table>
</div>

Thank you so much for your help!

Some of this code came from this answer here: How to integrate :missed days with :committed days in habits.rb? which messed up what worked with this answer: How to Make :level Change Based on :committed Days?


回答1:


It appears that date_started is an attribute of your Habit model, probably a database column, and that there are NULLs in date_started. Open up your Rails console and see if this is the case with:

Habit.where(date_started: nil).count

If you expect that date_started should never be null, add a validation to ensure that is the case. As soon as you test the code which is saving nulls into that column, the validation errors will point you to the bug.

On the other hand, if you want to allow nulls in date_started, then rewrite your levels method to allow for that.



来源:https://stackoverflow.com/questions/28933272/how-to-fix-level-rb-to-work-with-committed-days

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