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

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
4

2 に答える 2

5

ここには2つの問題があります。1つは知っていること、もう1つはおそらく知らないことです。

最初の問題は、view_countデフォルト値がないため、最初はnil。したがって、初めて更新しようとすると、次のようになりview_countます。

count = nil + 1

どういう意味nilかわかりません。+呼び出すnil.to_iとゼロになるので、これを行うことができます。

count = view_count.to_i + 1

もう1つの問題は、競合状態にあることです。2つのプロセスが同時に同じものを表示することになった場合、次の一連のイベントが発生する可能性があります。

  1. プロセス1(P1)はデータベースからプルview_countします。
  2. プロセス2(P2)はデータベースからプルview_countします。
  3. P1はデータベースに送り返しview_count+1ます。
  4. P2はデータベースに送り返しますが、 3view_count+1からの増分は含まれません。

これを解決する最も簡単な方法は、次を使用すること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ます。reload最新のものにしたい場合、view_countまたは単に追加して変更されたハッシュタグを保存しない場合は、を含めることもできます。

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

最初のもの(with )は、コールバックself.reloadに関連付けられている場合に問題を引き起こします。おそらく、コールバックをトリガーし、別のコールバックをトリガーして、コールバックをトリガーします...Rubyが無限再帰に腹を立て始めるまで。ただし、コールバックに結び付けるのではなく、手動で呼び出す場合は正常に機能するはずです(以下を参照)。after_findself.reloadafter_findself.reloadupdate_view_count

このself.view_count += 1バージョンでは、いくつかの増分を省略できますが、増分が欠落する余地が常にあるため、おそらく大したことではありません(もちろん、ビュー数を更新している場合を除きます)。

ただし、コールバックを使用することは、この種のことには適していないと思います。データベースからロードするが、インクリメントしHashtagたくない場合があります。view_countカウンターをインクリメントするために明示的なメソッド呼び出しを要求することをお勧めします。そうすれば、誤ってインクリメントすることはありません。明示的な呼び出しを要求すると、無限再帰をトリガーするコールバックがないため、上記の最初のバージョン(with 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)ため、値を追加しようとしたときにエラーが発生したためです。演算子が行うこと||は、最初に左側の式を試行し、それがまたはである場合を除き、それを返しnilますfalse。またはの場合はnil、またはfalseの場合でも右側の値を返します。次のように、割り当てについても同じことができます。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

于 2012-12-07T06:22:10.387 に答える