Rails after_find回调用法

时间:2012-12-07 06:05:14

标签: ruby-on-rails postgresql

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

2 个答案:

答案 0 :(得分:4)

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

第一个问题是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 的增量。
  5. 解决此问题的最简单方法是使用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),因此尝试添加错误。 ||运算符执行的操作是先尝试左侧的表达式,然后返回该表达式,除非它是nilfalse。如果是nilfalse,则会返回右侧的值,即使它是nilfalse。你也可以为作业做同样的事情,如下:

false || true # returns true
nil || 'asdf' # returns asdf
false || nil # returns nil
aaa ||= 1 # assigns 1 to aaa, unless aaa has a value

正如您所发现的,通过设置默认值可以实现类似的效果,以防止nil