我试图在我的模型中使用回调after_find
,我在尝试让它实际更新它在after_find
方法中找到的行时遇到问题。它抛出了无方法错误
错误
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
答案 0 :(得分:4)
这里有两个问题,一个是你知道的,一个是你可能不知道的。
第一个问题是view_count
没有默认值,所以它从nil
开始。因此,当您第一次尝试更新view_count
时,您最终会这样做:
count = nil + 1
和nil
不知道+
的含义。调用nil.to_i
会给你零,所以你可以这样做:
count = view_count.to_i + 1
另一个问题是你有竞争条件。如果两个进程最终同时查看同一个进程,那么您最终可以得到这一系列事件:
view_count
拉出数据库。view_count
拉出数据库。view_count+1
发回数据库。view_count+1
发送回数据库,但不会包含 3 的增量。解决此问题的最简单方法是使用increment_counter
:
def update_view_count
Hashtag.increment_counter(:view_count, self.id)
end
这将直接进行
update hashtags set view_count = coalesce(view_count, 0) + 1
数据库中的因此竞争条件与nil
问题一样消失。如果您想拥有最新的view_count
,或者只是添加一个而不保存修改后的标签,您还可以添加reload
:
def update_view_count
Hashtag.increment_counter(:view_count, self.id)
self.reload
end
# or
def update_view_count
Hashtag.increment_counter(:view_count, self.id)
self.view_count += 1 # And don't save it or you'll overwrite the "safe" value!
end
第一个(使用self.reload
)会在与after_find
回调绑定时导致问题:self.reload
可能会触发after_find
回调,这将触发另一个self.reload
回调1}}将触发回调...直到Ruby开始对无限递归感到不安。但是,如果您手动调用update_view_count
而不是将其绑定到回调(见下文),它应该可以正常工作。
self.view_count += 1
版本可以省略一些增量,但这可能不是什么大问题,因为你总是有空间丢失增量(除非你对视图计数有实时更新)。
我不认为使用回调对于这类事情是个好主意。有时会从数据库中加载Hashtag
,但您不希望view_count
递增。你最好不要求一个显式的方法调用来增加计数器,这样你就不会意外地增加一些东西。要求显式调用将允许您使用上面self.reload
的第一个版本(update_view_count
),因为您没有回调触发无限递归。
答案 1 :(得分:1)
试试这个:
def update_view_count
count = ( view_count || 0 ) + 1
view_count = count
save!
end
或者甚至是这样:
def update_view_count
update_attribute :view_count , ( view_count || 0 ) + 1
end
错误的原因是没有值(nil),因此尝试添加错误。 ||
运算符执行的操作是先尝试左侧的表达式,然后返回该表达式,除非它是nil
或false
。如果是nil
或false
,则会返回右侧的值,即使它是nil
或false
。你也可以为作业做同样的事情,如下:
false || true # returns true
nil || 'asdf' # returns asdf
false || nil # returns nil
aaa ||= 1 # assigns 1 to aaa, unless aaa has a value
正如您所发现的,通过设置默认值可以实现类似的效果,以防止nil
。