Im trying to use the call back after_find in my model, I’m having issues trying to get it to actually update the rows that it found in the after_find method. It’s throwing a no method error
error
Completed 500 Internal Server Error in 299ms
ActionView::Template::Error (undefined method `+' for nil:NilClass):
1: <div id="hashtags" class="twitter-hashtag-voting-block-v1">
2: <% @random_hashtag_pull.each do |hashtag| %>
3: <div class="span4 twitter-spans-v1" id="<%= hashtag.id %>">
4: <div id="tweet-block-v1" class="hashtag-tweet-database-container">
5: <div class="tweet-block-border-v1">
app/models/hashtag.rb:46:in `update_view_count'
app/views/shared/_vote_tweets.html.erb:2:in `_app_views_shared__vote_tweets_html_erb__2738953379660121418_70243350609340'
app/views/hashtags/create.js.erb:2:in `_app_views_hashtags_create_js_erb___1440072038737667206_70243345272440'
app/controllers/hashtags_controller.rb:23:in `create'
hashtag_controller
class HashtagsController < ApplicationController
def home
end
def vote
@random_hashtags = Hashtag.order("RANDOM()").limit(4)
end
def show
end
def index
end
def create
Hashtag.pull_hashtag(params[:hashtag])
@random_hashtag_pull = Hashtag.random_hashtags_pull
respond_to do |format|
format.html { redirect_to vote_path }
format.js
end
end
end
hashtag.rb
class Hashtag < ActiveRecord::Base
attr_accessible :text, :profile_image_url, :from_user, :created_at, :tweet_id, :hashtag, :from_user_name, :view_count
after_find :update_view_count
def self.pull_hashtag(hashtag)
dash = "#"
@hashtag_scrubbed = [dash, hashtag].join
Twitter.search("%#{@hashtag_scrubbed}", :lang => "en", :count => 100, :result_type => "mixed").results.map do |tweet|
unless exists?(tweet_id: tweet.id)
create!(
tweet_id: tweet.id,
text: tweet.text,
profile_image_url: tweet.user.profile_image_url,
from_user: tweet.from_user,
from_user_name: tweet.user.name,
created_at: tweet.created_at,
hashtag: @hashtag_scrubbed
)
end
end
end
def self.random_hashtags_pull
Hashtag.where{ |hashtag| hashtag.hashtag =~ @hashtag_scrubbed}.order{"RANDOM()"}.limit(4)
end
def update_view_count
count = (view_count + 1)
view_count = count
save!
end
end
You have two problems here, one you know about and one you probably don’t know about.
The first problem is that
view_counthas no default value so it starts out asnil. So the first time you try to update theview_count, you end up doing:and
nildoesn’t know what+means. Callingnil.to_igives you zero so you can do this:The other problem is that you have a race condition. If two processes end up viewing the same thing at the same time then you can end up with this sequence of events:
view_countout of the database.view_countout of the database.view_count+1back into the database.view_count+1back into the database but it won’t include the increment from 3.The easiest way to solve this is to use
increment_counter:That will do a direct
in the database so the race condition goes away as does the
nilproblem. You could also include areloadif you wanted to have an up-to-dateview_countor just add one and don’t save the modified Hashtag:The first one (with
self.reload) will cause problems when tied to anafter_findcallback: theself.reloadwill probably trigger theafter_findcallback which will trigger anotherself.reloadwhich will trigger the callback … until Ruby starts getting upset about infinite recursion. But, it should work fine if you manually callupdate_view_countrather than tying it to a callback (see below).The
self.view_count += 1version can leave out some increments but that’s probably not a big deal as you’ll always have room for missing increments (unless you have live-updating on the view counts of course).I don’t think using a callback is a good idea for this sort of thing though. There will be times when you load a
Hashtagout of the database but you don’t want theview_countto increment. You’d be better off requiring an explicit method call to increment the counter, that way you won’t increment things accidentally. Requiring an explicit call would allow you to use the first version (withself.reload) ofupdate_view_countabove as you wouldn’t have the callback triggering the infinite recursion.