0
votes

schema:

  create_table "posts", force: true do |t|
    t.string   "title"
    t.text     "content"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "user_id"
    t.integer  "total_stars"
    t.integer  "average_stars"
  end

  create_table "stars", force: true do |t|
    t.integer  "starable_id"
    t.string   "starable_type"
    t.integer  "user_id"
    t.integer  "number"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "stars", ["starable_id", "starable_type"], name: "index_stars_on_starable_id_and_starable_type"

  create_table "users", force: true do |t|
    t.string   "name"
    t.string   "email"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

models:

class Post < ActiveRecord::Base
  has_many :stars, :as => :starable, :dependent => :destroy 
  belongs_to :user
end

class Star < ActiveRecord::Base
  before_create :add_to_total_stars

  belongs_to :starable, :polymorphic => true

  protected

  def add_to_total_stars
    if [Post].include?(starable.class)
      self.starable.update_column(:total_stars, starable.total_stars + self.number)
    end
  end
end

class User < ActiveRecord::Base
  has_many :posts, dependent: :destroy
  has_many :votes, dependent: :destroy
end

So I tried creating a star in the Rails console like this:

post = Post.first
user = User.first
star = post.stars.build(number: 1)
star.user_id = user.id

And everything goes OK 'till here. But when I try to save it:

star.save

I get this error:

NoMethodError: undefined method +' for nil:NilClass from /home/alex/rails/rating/app/models/star.rb:10:inadd_to_total_stars' from /home/alex/.rvm/gems/ruby-1.9.3-p0/gems/activesupport-4.0.0/lib/active_support/callbacks.rb:377:in _run__956917800__create__callbacks' from /home/alex/.rvm/gems/ruby-1.9.3-p0/gems/activesupport-4.0.0/lib/active_support/callbacks.rb:80:in run_callbacks' from /home/alex/.rvm/gems/ruby-1.9.3-p0/gems/activerecord-4.0.0/lib/active_record/callbacks.rb:303:in create_record' from /home/alex/.rvm/gems/ruby-1.9.3-p0/gems/activerecord-4.0.0/lib/active_record/timestamp.rb:57:increate_record' from /home/alex/.rvm/gems/ruby-1.9.3-p0/gems/activerecord-4.0.0/lib/active_record/persistence.rb:466:in create_or_update' from /home/alex/.rvm/gems/ruby-1.9.3-p0/gems/activerecord-4.0.0/lib/active_record/callbacks.rb:299:inblock in create_or_update' from /home/alex/.rvm/gems/ruby-1.9.3-p0/gems/activesupport-4.0.0/lib/active_support/callbacks.rb:383:in _run__956917800__save__callbacks' from /home/alex/.rvm/gems/ruby-1.9.3-p0/gems/activesupport-4.0.0/lib/active_support/callbacks.rb:80:in run_callbacks'

What could be the cause?

(I'm using Rails 4)

2

2 Answers

1
votes

It looks like the value of Post.first.total_stars is nil. Going by your schema and model examples you allow null in the database and do not validate it's presence in ActiveRecord.

If it makes sense to default this value to 0, then you should set the default value in the schema.

So I would add the following to your schema:

create_table "posts", force: true do |t|
  # ...
  t.integer  "total_stars", null: false, default: 0
end

And the following validation in your model:

class Post < ActiveRecord::Base
  has_many :stars, :as => :starable, :dependent => :destroy 
  belongs_to :user

  validates :total_stars, presence: true
end

As an aside, I would get rid of total_stars altogether and let rails do this for you with the counter_cache option instead. Here's the Railscast screencast to get you started.

1
votes

you are getting that error because starable.total_stars is nil in your callback method. you need to ensure that starable.total_stars is set to 0 ro you can call to_i method on it (nil.to_i #=> 0) to ensure that you have 0 if it is not initialized