Nick Res
Nick Res

Reputation: 2244

object existence not found in console but found when called in view?

In my application I have a user model with a has many relationship to a status_updates model.

Currently, my database only has 1 user and zero status_updates saved.

What is bizarre is that when I search for status_updates or for a relationship between these objects in consol, affirms that are nil status_updates in the database.

StatusUpdate.count
(0.2ms)  SELECT COUNT(*) FROM "status_updates" 
=> 0 

StatusUpdate.any?
(0.1ms)  SELECT COUNT(*) FROM "status_updates" 
=> false 



user.status_updates.count
(0.2ms)  SELECT COUNT(*) FROM "status_updates" WHERE "status_updates"."user_id" = 1
=> 0 

user.status_updates.any?
(0.2ms)  SELECT COUNT(*) FROM "status_updates" WHERE "status_updates"."user_id" = 1
=> false 

So, that's clear for me.

BUT, when in my application I write the following, a status_update object is returned!

def end_date             
 if self.status_updates.any? == false
  return self.status_updates.any?
 elsif self.status_updates.any? == true
  return self.status_updates.first
 end
end  

This is the call in my view

current_user.end_date

And this is what is returned to the view:

#<StatusUpdate:0x007fa99765d6f8>

And if I change the call in the view to this:

current_user.status_updates.first

=> <StatusUpdate:0x007fa99740b5f8>

But, if I call this:

current_user.status_updates.count

=> 0

current_user.status_updates.any?

=> true

In the view when I just use current_user.status_updates it returns

[#<StatusUpdate id: nil, created_at: nil, updated_at: nil, user_id: 1, current_weight: 0.0, current_bf_pct: 0.0, current_lbm: 0.0, current_fat_weight: 0.0, change_in_weight: nil, change_in_bf_pct: nil, change_in_lbm: nil, change_in_fat_weight: nil, total_weight_change: nil, total_bf_pct_change: nil, total_lbm_change: nil, total_fat_change: nil>]

What's going on here?!


User Model relationship

has_many :status_updates, dependent: :destroy

Status Update Model Relationship

belongs_to :user


Status Update Model

class StatusUpdate < ActiveRecord::Base
 belongs_to :user

 after_initialize :default_values 
 before_save :sanitize 

 attr_accessible :current_weight,
               :current_bf_pct,
               :current_lbm,
               :current_fat_weight,
               :change_in_weight,
               :change_in_bf_pct,
               :change_in_lbm,
               :change_in_fat_weight,
               :total_weight_change,
               :total_bf_pct_change,
               :total_lbm_change,
               :total_fat_change,
               :created_at

 validates :user_id,            presence: true
 validates :current_bf_pct,     presence: true, numericality: true, length: { minimum: 2, maximum:5 }  
 validates :current_weight,     presence: true, numericality: true, length: { minimum: 2, maximum:5 } 
 validates :current_lbm,        presence: true
 validates :current_fat_weight, presence: true                   


 def sanitize     
 if self.current_bf_pct >= 0.5
   self.current_bf_pct /= 100
    if self.current_bf_pct <= 0.04
      self.current_fb_pct *= 100
    end 
 end
 self.current_fat_weight = self.current_weight * self.current_bf_pct
 self.current_lbm = self.current_weight - self.current_fat_weight
 end  

 def default_values
 if self.created_at == nil 
  self.current_bf_pct       = 0.20 
  self.current_weight       = 0 
  self.current_lbm          = 0 
  self.current_fat_weight   = 0 
  self.change_in_weight     = 0 
  self.change_in_bf_pct     = 0 
  self.change_in_lbm        = 0 
  self.change_in_fat_weight = 0 
  self.total_weight_change  = 0 
  self.total_bf_pct_change  = 0 
  self.total_lbm_change     = 0 
  self.total_fat_change     = 0 
  end
 end

  def previous_status_update
   previous_status_update = user.status_updates.where( "created_at < ? ", self.created_at ).first   
 if previous_status_update == nil
   return self
 else
   previous_status_update
 end
end 


 default_scope order: 'status_updates.created_at DESC'

end

User Model:

    class User < ActiveRecord::Base
      # Include default devise modules. Others available are:
      # :token_authenticatable, :confirmable,
      # :lockable, :timeoutable and :omniauthable
      devise :database_authenticatable, :registerable,
            :recoverable, :rememberable, :trackable, :validatable

      before_create :sanitize

      has_many :status_updates, dependent: :destroy
      has_many :meals, dependent: :destroy
      has_many :custom_foods, dependent: :destroy
      has_many :meal_foods, through: :meals
      # after_initialize :default_values
      attr_accessor :user_password, :user_password_confirmation, :current_password
      attr_accessible :email,
                      :password,
                      :password_confirmation,
                      :current_password,
                      :goal,
                      :measurement,
                      :bmr_formula,
                      :fat_factor,
                      :protein_factor,
                      :remember_me,
                      :deficit_amnt,
                      :target_bf_pct,
                      :activity_factor,
                      :current_password

      validates :email,                 presence: true
      validates :target_bf_pct,         presence: true, on: :update, length: { minimum: 3, maximum: 4 }
      validates :activity_factor,       presence: true, on: :update
      validates :deficit_amnt,          presence: true, on: :update
      validates :fat_factor,            presence: true, on: :update
      validates :protein_factor,        presence: true, on: :update

      def new?
        self.created_at <= 1.minutes.ago.to_date ? true : false
      end

      def sanitize
        #inputs
        self.activity_factor       = 1.3
        self.deficit_amnt          = 1
        self.target_bf_pct         = 10 
        self.fat_factor            = 0.45
        self.protein_factor        = 1
      end


      def end_date             
        if self.status_updates.any? == false
          #Time.now
          self.status_updates.any?
        elsif self.status_updates.any? == true
          #(self.start_date + self.weeks_to_goal.to_i.weeks).strftime("%m/%d/%Y")
          self.status_updates
        end
      end

      def start_date           
        if self.status_updates.any? == true
          self.status_updates.first.created_at
        end
      end

      def daily_caloric_deficit 
        self.tdee.to_d - self.daily_intake.to_d
      end

      def current_fat_weight   
        BigDecimal(self.latest_status_update.current_fat_weight, 4)
      end

      def current_lbm          
        BigDecimal(self.latest_status_update.current_lbm, 4)
      end

      def current_bf_pct       
        BigDecimal(self.latest_status_update.current_bf_pct * 100, 4)
      end

      def current_weight       
        BigDecimal(self.latest_status_update.current_weight, 4)
      end

      def total_weight         
        self.latest_status_update.current_weight
      end

    #  def lbm                  
    #    self.latest_status_updates.current_lbm
    #  end

      def recent_weight_change 
        BigDecimal(self.latest_status_update.current_weight - self.latest_status_update.previous_status_update.current_weight, 2) 
      end

      def recent_lbm_change   
        BigDecimal(self.latest_status_update.current_lbm - self.latest_status_update.previous_status_update.current_lbm, 2)
      end

      def recent_fat_change
        BigDecimal(self.latest_status_update.current_fat_weight - self.latest_status_update.previous_status_update.current_fat_weight, 3)
      end

      def total_lbm_change
        BigDecimal(self.latest_status_update.current_lbm - self.oldest_status_update.current_lbm, 3)
      end

      def total_fat_change 
        BigDecimal(self.latest_status_update.current_fat_weight - self.oldest_status_update.current_fat_weight, 3)
      end

      def total_weight_change
        BigDecimal(self.latest_status_update.current_weight - self.oldest_status_update.current_weight, 3)
      end

      def last_date
        self.status_updates.last.created_at.strftime("%m/%d/%Y") 
      end

      def beginning_date
        self.status_updates.first.created_at.strftime("%m/%d/%Y") 
      end

      def latest_status_update
        self.status_updates.first  
      end

      def oldest_status_update
        self.status_updates.last
      end

      def bmr
        cur_lbm = self.current_lbm
        cur_lbm *= 0.45
        '%.2f' % (370 + (21.6 * cur_lbm.to_d))
      end

      def target_weight
        tar_bf_pct = self.target_bf_pct /= 100
        '%.2f' %  ((self.total_weight * tar_bf_pct)+ self.current_lbm)
      end 

      def fat_to_burn
        '%.2f' % (self.total_weight.to_d - self.target_weight.to_d)
      end

      def tdee
        '%.2f' % (self.bmr.to_d * self.activity_factor.to_d)
      end

      def deficit_pct
        daily_cal_def = ((self.deficit_amnt.to_f * 3500)/7)
        (daily_cal_def.to_d/self.tdee.to_d)
      end

      def daily_calorie_burn
        '%.2f' % (self.tdee.to_d * self.deficit_pct.to_d)  
      end

      def weekly_calorie_burn_rate
        '%.2f' % (self.daily_calorie_burn.to_d*7) 
      end

      def weeks_to_goal
        '%.2f' %  (self.fat_to_burn.to_d*3500/self.weekly_calorie_burn_rate.to_d) 
      end                  

      def daily_intake
        '%.2f' % (self.tdee.to_d - self.daily_calorie_burn.to_d)
      end                       

      def total_grams_of(macro)
        self.meal_foods.map(&macro).inject(:+)
      end 

      def pct_fat_satisfied
        #how much of a macro is needed?
        fat_needed = self.fat_factor * self.current_lbm
        #how much is in the meal?
        fat_provided = self.total_grams_of(:fat)
        #percent needed
        pct_fulfilled = fat_provided.to_f/fat_needed.to_f
        BigDecimal(pct_fulfilled, 2)*100
      end 

      def pct_protein_satisfied
        #how much protien is needed?
        protein_needed = self.protein_factor * self.current_lbm
        #how much protien is provided?
        protein_provided = total_grams_of(:protien)
        #pct of protien satisfied?
        pct_fulfilled = protein_provided.to_f/protein_needed.to_f
        BigDecimal(pct_fulfilled, 2)*100
      end    

      def pct_carbs_satisfied
        #how many carbs are needed?
        cals_required = self.tdee.to_f - (self.tdee.to_f * self.deficit_pct.to_f)
        fat_cals = total_grams_of(:fat) * 9
        protien_cals = total_grams_of(:protien) * 4
        #how many carbs are provided?
        cals_provided = fat_cals + protien_cals
        cals_balance = cals_required - cals_provided
        carbs_needed = cals_balance/4
        carbs_provided = total_grams_of(:carbs)
        BigDecimal(carbs_provided / carbs_needed, 2) * 100
      end 

    end

I've tried resetting my database and the same issue comes up.


To recap:

It seems that a status_update is being initialized, and this initialized version of the object is what is available to the view. All the attributes are the initialized ones, which is why it doesn't have an id, or a timestamp for when it was created... because it never was created.

however when I try to access it from console I realized that it's giving me 'nil' and all the predictable values because in console it's looking for an already created and object relationship in the relationship.

So the more precise question here is the view returning the initialized version of the object instead of what console returns?

When the app hits a method within the user model it throws an error because the status_update doesn't exist when it calls something like

self.status_updates.first

Upvotes: 1

Views: 173

Answers (2)

John Hinnegan
John Hinnegan

Reputation: 5962

This line:

#<StatusUpdate id: nil, created_at: nil, updated_at: nil, user_id: 1, current_weight: 0.0, current_bf_pct: 0.0, current_lbm: 0.0, current_fat_weight: 0.0, change_in_weight: nil, change_in_bf_pct: nil, change_in_lbm: nil, change_in_fat_weight: nil, total_weight_change: nil, total_bf_pct_change: nil, total_lbm_change: nil, total_fat_change: nil>]

The id: nil indicates that this model hasn't been saved yet. Models only get an id once you save them. The fact that it has user_id: 1 indicates that you probably created this through the user, as Frederick indicated. So somewhere you're creating this instance and not saving it.

PS, it's broadly considered poor ruby form to check if booleans are false or true. e.g.

don't do this:

self.status_updates.any? == false

do

self.status_updates.any?

which is true or false anyway, then just use if and unless as your checks. Better still, self is assumed, so in your case, you can just do:

status_updates.any?

PPS active_record defines a new? method for exactly this use case. A record that has not yet been saved is new and so new? will return true. I strongly discourage you from overwriting this method, or anything else defined by active_record.

Upvotes: 1

Frederick Cheung
Frederick Cheung

Reputation: 84162

You're probably doing

@status_update = user.status_updates.build

in your controller - this is pretty common place so that you can then put a form on the page that allows the user to submit a new status update.

This object is unsaved, so

user.status_updates.count

which is guaranteed to hit the database (and run select count(*) ...) doesn't count it. It is however in the in memory cache of the status updates association for that user and so user.status_updates.any? returns true.

You might also note that it's extremely unidiomatic to compare the return value of predicate methods with true or false. It reads funny and methods like this may return a value with equivalent truthiness other than true or false (for example nil instead of false).

Upvotes: 1

Related Questions