the*_*s11 2 postgresql ruby-on-rails
我试图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'
Run Code Online (Sandbox Code Playgroud)
标签控制器
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
Run Code Online (Sandbox Code Playgroud)
标签.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
Run Code Online (Sandbox Code Playgroud)
这里有两个问题,一个你知道,一个你可能不知道。
第一个问题是它view_count没有默认值,所以它以nil. 因此,当您第一次尝试更新 时view_count,您最终会执行以下操作:
count = nil + 1
Run Code Online (Sandbox Code Playgroud)
并且nil不知道是什么+意思。打电话nil.to_i给你零,所以你可以这样做:
count = view_count.to_i + 1
Run Code Online (Sandbox Code Playgroud)
另一个问题是你有一个竞争条件。如果两个进程最终同时查看同一件事,那么您最终会得到以下事件序列:
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
Run Code Online (Sandbox Code Playgroud)
这将做一个直接
update hashtags set view_count = coalesce(view_count, 0) + 1
Run Code Online (Sandbox Code Playgroud)
在数据库中,因此竞争条件和nil问题一样消失。reload如果您想要一个最新的view_count或者只是添加一个而不保存修改后的 Hashtag,您也可以包含一个:
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
Run Code Online (Sandbox Code Playgroud)
第一个(with self.reload)在绑定到after_find回调时会导致问题:self.reload可能会触发after_find回调,这将触发另一个self.reload将触发回调......直到 Ruby 开始对无限递归感到不安。但是,如果您手动调用update_view_count而不是将其绑定到回调(见下文),它应该可以正常工作。
该self.view_count += 1版本可能会遗漏一些增量,但这可能没什么大不了的,因为您总是有丢失增量的空间(当然,除非您对视图计数进行实时更新)。
我不认为使用回调是处理这类事情的好主意。有时您会Hashtag从数据库中加载 a但您不想view_count增加。你最好要求一个显式的方法调用来增加计数器,这样你就不会意外地增加东西。要求显式调用将允许您使用上面的第一个版本(带有self.reload),update_view_count因为您不会让回调触发无限递归。
| 归档时间: |
|
| 查看次数: |
372 次 |
| 最近记录: |