2

我试图在我的模型中使用回调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

标签.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
4

2 回答 2

5

这里有两个问题,一个是你知道的,一个是你可能不知道的。

第一个问题是它view_count没有默认值,所以它以nil. 因此,当您第一次尝试更新 时view_count,您最终会这样做:

count = nil + 1

而且nil不知道是什么+意思。调用nil.to_i给你零,所以你可以这样做:

count = view_count.to_i + 1

另一个问题是你有一个竞争条件。如果两个进程最终同时查看同一事物,那么您可能会遇到以下事件序列:

  1. 进程一 ( P1 )view_count从数据库中拉出。
  2. 进程二 ( P2 )view_count从数据库中拉出。
  3. P1发送view_count+1回数据库。
  4. P2发送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

在数据库中,因此竞争条件和问题一样消失nilreload如果您想要一个最新的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

第一个(带self.reload)在绑定到after_find回调时会导致问题:self.reload可能会触发after_find回调,这将触发另一个self.reload将触发回调的回调......直到 Ruby 开始对无限递归感到不安。但是,如果您手动调用update_view_count而不是将其绑定到回调,它应该可以正常工作(见下文)。

self.view_count += 1版本可以省略一些增量,但这可能不是什么大问题,因为您总是有空间丢失增量(当然,除非您对视图计数进行实时更新)。

我不认为使用回调对于这类事情是一个好主意。有时您会Hashtag从数据库中加载 a 但您不希望view_count增加 a。你最好要求一个显式的方法调用来增加计数器,这样你就不会意外地增加东西。要求显式调用将允许您使用上面的第一个版本(带有self.reload),update_view_count因为您不会让回调触发无限递归。

于 2012-12-07T06:27:38.557 回答
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),因此尝试添加错误。运算符所做的||是首先尝试左边的表达式,然后返回它,除非它是nilor false。如果是nilor false,它会返回右边的值,即使是nilor 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.

于 2012-12-07T06:22:10.387 回答