Search code examples
ruby-on-railsrubybooleannested-attributessidebar

How to .count nested_attribute boolean?


The nested attribute of quantifieds are results. In the results partial a User will checkoff if their quantified result is a :good thing or not.

In the sidebar I want to .count how many good results the User has marked off to serve as a reference point of their success.

SIDEBAR SECTION: layouts/_count.html.erb

<div class="stats">
  <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.quantifieds.count %> #Works
    </strong>
    Quantified
  </a>
  &nbsp;
  <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.results.good.count %> #Remains zero regardless of number of checked :good boxes
    </strong>
    Good
  </a>
</div>

quantifieds/_result_fields.html.erb

<div class="nested-fields">
  <div class="form-group">
    <%= f.text_field :result_value, class: 'form-control', placeholder: 'Enter Result' %>
    <br/>
		<%= f.date_select :date_value, :order => [:month, :day, :year], :with_css_classes => true, :class => "modular-date-field" %>
    <b><%= link_to_remove_association "Remove Result", f %></b>
  <div class="america3">
  <label> Good: </label>
  <%= f.check_box :good %>
  </div>
  </div>
</div>

result.rb

class Result < ActiveRecord::Base
	belongs_to :user
  belongs_to :quantified
  default_scope { order('date_value DESC') }
	scope :good, -> { where(good: true) }
	scope :bad, -> { where(good: false) }
end

Should we add a method to the application controller?

class ApplicationController < ActionController::Base
  before_action :load_todays_habits
  before_action :set_top_3_goals
  before_action :randomize_value
  before_action :set_stats
  protect_from_forgery with: :exception
  include SessionsHelper

  def set_top_3_goals
    @top_3_goals = current_user.goals.unaccomplished.top_3 if current_user
  end

  def randomize_value
    @sidebarvaluations = current_user.valuations.randomize if current_user
  end

  def set_stats
    @quantifieds = Quantified.joins(:results).all
    @averaged_quantifieds = current_user.quantifieds.averaged if current_user
    @instance_quantifieds = current_user.quantifieds.instance if current_user
  end

  private 

  def load_todays_habits
    @user_tags = current_user.habits.committed_for_today.tag_counts if current_user
    @all_tags  = Habit.committed_for_today.tag_counts if current_user
  end

  # Confirms a logged-in user.
  def logged_in_user
    unless logged_in?
      store_location
      flash[:danger] = "Please log in."
      redirect_to login_url
    end
  end
end

class QuantifiedsController < ApplicationController
  before_action :set_quantified, only: [:show, :edit, :update, :destroy]
  before_action :logged_in_user, only: [:create, :destroy]

  def index
    if params[:tag]
      @quantifieds = Quantified.tagged_with(params[:tag])
    else
      @quantifieds = Quantified.joins(:results).all
      @averaged_quantifieds = current_user.quantifieds.averaged
      @instance_quantifieds = current_user.quantifieds.instance
    end
  end

  def show
  end

  def new
    @quantified = current_user.quantifieds.build 
  end

  def edit
  end

  def create
    @quantified = current_user.quantifieds.build(quantified_params)
    if @quantified.save
      redirect_to quantifieds_url, notice: 'Quantified was successfully created'
    else
      @feed_items = []
      render 'pages/home'
  end
end

  def update
    if @quantified.update(quantified_params)
      redirect_to quantifieds_url, notice: 'Goal was successfully updated'
    else
      render action: 'edit'
  end
end

  def destroy
    @quantified.destroy
    redirect_to quantifieds_url
  end

  private
    def set_quantified
      @quantified = Quantified.find(params[:id])
    end

    def correct_user
      @quantified = current_user.quantifieds.find_by(id: params[:id])
      redirect_to quantifieds_path, notice: "Not authorized to edit this goal" if @quantified.nil?
    end

    def quantified_params
      params.require(:quantified).permit(:categories, :metric, :result, :date, :comment, :private_submit, :tag_list, :good, results_attributes: [:id, :result_value, :date_value, :good, :_destroy])
    end
end

quantifieds/_form

<%= javascript_include_tag "quantified.js" %>

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

<div class="america">
<form>
  
  <% Quantified::CATEGORIES.each do |c| %>&nbsp;
    <%= f.radio_button(:categories, c, :class => "date-format-switcher") %>&nbsp;
    <%= label(c, c) %>
  <% end %>
      <br/>
      <br/>
      <div class="form-group">
        <%= f.text_field :tag_list, quantified: @quantified.tag_list.to_s.titleize, class: 'form-control', placeholder: 'Enter Action' %>
      </div>
      <div class="form-group">
        <%= f.text_field :metric,  class: 'form-control', placeholder: 'Enter Metric' %>
      </div>

    <div id="results">
      <%= f.fields_for :results do |result| %>
      <%= render 'result_fields', :f => result %>
      <% end %>
    </div>
    
    <div class="links">
      &nbsp;&nbsp;<b><%= link_to_add_association 'Add Result', f, :results %></b>
    </div>

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

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

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

  <label> Private: </label>
  <%= f.check_box :private_submit %>
  
</form>
</div>
<% end %>

quantifieds.rb

class Quantified < ActiveRecord::Base
	belongs_to :user
 	has_many :results #correct
	has_many :comments, as: :commentable
	accepts_nested_attributes_for :results, :reject_if => :all_blank, :allow_destroy => true #correct
 	scope :averaged,  -> { where(categories: 'Averaged') }
 	scope :instance,  -> { where(categories: 'Instance') }
	scope :private_submit, -> { where(private_submit: true) }
	scope :public_submit, -> { where(private_submit: false) }
 	validates :categories, :metric, presence: true
	acts_as_taggable

	CATEGORIES = ['Averaged', 'Instance']
end

schema.rb

# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20150317171422) do

  create_table "activities", force: true do |t|
    t.integer  "trackable_id"
    t.string   "trackable_type"
    t.integer  "owner_id"
    t.string   "owner_type"
    t.string   "key"
    t.text     "parameters"
    t.integer  "recipient_id"
    t.string   "recipient_type"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "activities", ["owner_id", "owner_type"], name: "index_activities_on_owner_id_and_owner_type"
  add_index "activities", ["recipient_id", "recipient_type"], name: "index_activities_on_recipient_id_and_recipient_type"
  add_index "activities", ["trackable_id", "trackable_type"], name: "index_activities_on_trackable_id_and_trackable_type"

  create_table "comments", force: true do |t|
    t.text     "content"
    t.integer  "commentable_id"
    t.string   "commentable_type"
    t.datetime "created_at",       null: false
    t.datetime "updated_at",       null: false
  end

  add_index "comments", ["commentable_id", "commentable_type"], name: "index_comments_on_commentable_id_and_commentable_type"

  create_table "days", force: true do |t|
    t.integer  "level_id"
    t.integer  "habit_id"
    t.boolean  "missed",     default: false
    t.datetime "created_at",                 null: false
    t.datetime "updated_at",                 null: false
  end

  create_table "goals", force: true do |t|
    t.string   "name"
    t.date     "deadline"
    t.boolean  "accomplished"
    t.boolean  "private_submit"
    t.integer  "user_id"
    t.datetime "created_at",     null: false
    t.datetime "updated_at",     null: false
  end

  add_index "goals", ["user_id", "created_at"], name: "index_goals_on_user_id_and_created_at"
  add_index "goals", ["user_id"], name: "index_goals_on_user_id"

  create_table "habits", force: true do |t|
    t.datetime "left"
    t.integer  "level"
    t.text     "committed"
    t.datetime "date_started"
    t.string   "trigger"
    t.string   "target"
    t.string   "reward"
    t.boolean  "private_submit"
    t.integer  "user_id"
    t.datetime "created_at",     null: false
    t.datetime "updated_at",     null: false
  end

  add_index "habits", ["user_id", "created_at"], name: "index_habits_on_user_id_and_created_at"
  add_index "habits", ["user_id"], name: "index_habits_on_user_id"

  create_table "levels", force: true do |t|
    t.integer  "user_id"
    t.integer  "habit_id"
    t.boolean  "passed",     default: false
    t.datetime "created_at",                 null: false
    t.datetime "updated_at",                 null: false
  end

  create_table "quantifieds", force: true do |t|
    t.string   "categories"
    t.string   "metric"
    t.boolean  "private_submit"
    t.integer  "user_id"
    t.datetime "created_at",     null: false
    t.datetime "updated_at",     null: false
  end

  add_index "quantifieds", ["user_id", "created_at"], name: "index_quantifieds_on_user_id_and_created_at"
  add_index "quantifieds", ["user_id"], name: "index_quantifieds_on_user_id"

  create_table "relationships", force: true do |t|
    t.integer  "follower_id"
    t.integer  "followed_id"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  add_index "relationships", ["followed_id"], name: "index_relationships_on_followed_id"
  add_index "relationships", ["follower_id", "followed_id"], name: "index_relationships_on_follower_id_and_followed_id", unique: true
  add_index "relationships", ["follower_id"], name: "index_relationships_on_follower_id"

  create_table "results", force: true do |t|
    t.integer  "user_id"
    t.string   "result_value"
    t.date     "date_value"
    t.integer  "quantified_id"
    t.boolean  "good"
    t.datetime "created_at",    null: false
    t.datetime "updated_at",    null: false
  end

  create_table "taggings", force: true do |t|
    t.integer  "tag_id"
    t.integer  "taggable_id"
    t.string   "taggable_type"
    t.integer  "tagger_id"
    t.string   "tagger_type"
    t.string   "context",       limit: 128
    t.datetime "created_at"
  end

  add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true
  add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context"

  create_table "tags", force: true do |t|
    t.string  "name"
    t.integer "taggings_count", default: 0
  end

  add_index "tags", ["name"], name: "index_tags_on_name", unique: true

  create_table "users", force: true do |t|
    t.string   "name"
    t.string   "email"
    t.text     "missed_days"
    t.text     "missed_levels"
    t.string   "provider"
    t.string   "uid"
    t.string   "oauth_token"
    t.datetime "oauth_expires_at"
    t.datetime "created_at",                        null: false
    t.datetime "updated_at",                        null: false
    t.string   "password_digest"
    t.string   "remember_digest"
    t.boolean  "admin",             default: false
    t.string   "activation_digest"
    t.boolean  "activated",         default: false
    t.datetime "activated_at"
    t.string   "reset_digest"
    t.datetime "reset_sent_at"
  end

  add_index "users", ["email"], name: "index_users_on_email", unique: true

  create_table "valuations", force: true do |t|
    t.string   "name"
    t.boolean  "private_submit"
    t.integer  "user_id"
    t.datetime "created_at",     null: false
    t.datetime "updated_at",     null: false
  end

  add_index "valuations", ["user_id", "created_at"], name: "index_valuations_on_user_id_and_created_at"
  add_index "valuations", ["user_id"], name: "index_valuations_on_user_id"

end

class User < ActiveRecord::Base
  has_many :authentications
  has_many :habits, dependent: :destroy
  has_many :levels
  has_many :valuations, dependent: :destroy
  has_many :comments, as: :commentable
  has_many :goals, dependent: :destroy
  has_many :quantifieds, dependent: :destroy
  has_many :results, dependent: :destroy
                               
                            

Thanks so much for your time!


Solution

  • You can update your Result as follows:

    class Result < ActiveRecord::Base
      # rest of the code
      scope :good,       -> { where(good: true) }
      scope :good_count, -> { good.count }
    end
    

    Let's perform some tests in rails console:

    u = User.create({ user_attributes })
    
    u.results.create(good: true)
    u.results.create(good: false)
    u.results.create(good: true)
    
    u.results.count
    # => SELECT COUNT(*) FROM "results" WHERE "results"."user_id" = ?  [["user_id", 1]]
    # => 3
    
    u.results.good_count
    # => SELECT COUNT(*) FROM "results" WHERE "results"."user_id" = ? AND "results"."good" = 't'  [["user_id", 1]]
    # => 2
    

    If you changed your User as follows:

    class User < ActiveRecord::Base
      # rest of the code
      def good_results_count
        results.good_count
      end
    end
    

    which is much cleaner solution, you can use it in your application like:

    # assuming we have data set like in previous step
    u = User.last
    
    u.good_results_count # you can use this line in your template
    # => SELECT COUNT(*) FROM "results" WHERE "results"."user_id" = ? AND "results"."good" = 't'  [["user_id", 1]]
    # => 2
    

    If, for some reason, the value remains 0 - as you mentioned - try performing the operations I described in console to see real data in database. Maybe this is not the problem with fetching proper data, maybe data is not saved correctly?

    Let me know how it goes, so we're try to address the real problem!

    Good luck!

    Update

    Problem in view is due to lack of proper association between User and Result. What currently is in User:

    class User < ActiveRecord::Base
      # rest of the code
      has_many :quantifieds, dependent: :destroy
      has_many :results, dependent: :destroy
      # rest of the code
    end
    

    When new Results are created through Quantifieds, this is what you have in QuantifiedsController#create:

    class QuantifiedsController < ApplicationController
      def create
        @quantified = current_user.quantifieds.build(quantified_params)
        if @quantified.save
          redirect_to quantifieds_url, notice: 'Quantified was successfully created'
        else
          @feed_items = []
          render 'pages/home'
        end
      end
    end
    

    Well, everything looks perfectly all right, but the problem is actually in line @quantified = current_user.quantifieds.build(quantified_params). All Results created this was have user_id = nil, only the quantified_id is set properly.

    How to fix this?

    By simplifying the associations! There is no need to store both quantify_id and user_id in Result.

    If there is a relation like: User has many Quantifies, and Quantify has many Results, you can quite easily access Results of Users by proper declaration in User:

    class User < ActiveRecord::Base
      # rest of the code
      has_many :quantifieds, dependent: :destroy
      has_many :results, through: :quantifieds
      # rest of the code
    end
    

    This is quite clever thing! Let's see, what happens under the hood:

    u = User.last
    u.results
    Result Load (0.3ms)  SELECT "results".* FROM "results" INNER JOIN "quantifieds" ON "results"."quantified_id" = "quantifieds"."id" WHERE "quantifieds"."user_id" = ?  ORDER BY date_value DESC  [["user_id", 5]]
    => #<ActiveRecord::Associations::CollectionProxy ...>
    

    Now, when relation is set up as described above, the proper counts should appear in website.

    There is no need to store user_id in Result, so you can freely remove it!