ActiveRecordでカウンター

呼ばれるたびに前回返した値から+1したものを返すカウンターのクラスを書いたものの、出番がなくなったが、今後同じようなものを書きそうなのでメモ。 動作はMySQLで試した。

テーブルの定義

class CreateCounters < ActiveRecord::Migration
  def change
    create_table :counters do |t|
      t.string :key
      t.integer :count, :default => 0
 
      t.timestamps
    end
 
    add_index :counters, :key
  end
end

クラス定義

class Counter < ActiveRecord::Base
  class << self
    def next(key)
      find_or_create_by_key(key).next
    end
  end
 
  validates_presence_of :key, :count
  validates_uniqueness_of :key
 
  def next
    with_lock do
      increment! :count
      count
    end
  end
end

使い方

# クラスメソッドで使う
Counter.next('foo') # => 1
Counter.next('foo') # => 2
Counter.next('foo') # => 3
# 違うキーを使うと別のカウンター扱いになる
Counter.next('bar') # => 1
 
# インスタンス化して使う
counter = Counter.find_or_create_by_key 'foo'
counter.next # => 4
# 同じキーで別のインスタンスを作ると互いに影響する
counter2 = Counter.find_or_create_by_key 'foo'
counter2.next # => 5
counter.next  # => 6
counter2.next # => 7