無効なユーザを削除する

Ruby on Rails Tutorial: Learn Rails by Exampleのサンプルアプリ「sample_app」に、ユーザアカウントのアクティベーション機能を追加した後*1、1日経過してもアクティベートしなかったアカウントは削除したいと思いました。

無効なユーザの取得

app/models/user.rbに無効なユーザを取得するscopeを追加。

scope :expired, where(["created_at <= ? and active = ?", 1.day.ago, false])

HerokuのCronジョブ

Herokuは、add-onでCron*2がありますので、以下のようなtaskをlib/tasks/cron.rakeを作成。

task :cron => :environment do
  if Time.now.hour == 0
    User.expired.destroy_all
  end
end

テスト

spec/models/user_spec.rbにテストを以下のように書きました。

describe "expired users" do
  before(:each) do
    @user = Factory(:user, :active => false, :created_at => 1.days.ago)
  end
 
  it "should exist" do
    User.should respond_to(:expired)
  end
 
  it "should destroy" do
    User.expired.destroy_all.should == [@user]
  end
end

しかし、失敗する。log/test.logを確認すると、ユーザ作成日時は「2010-09-13 21:12:10.253568」ですが、User.expiredで発行されているSELECT文のWHERE句を見ると「2010-09-13 21:12:09.338912」。ユーザの作成(INSERT)が終わる前に、検索(SELECT)が実行されているはなぜだろう。

  SQL (0.4ms)  INSERT INTO "users" ("activation_key", "active", "admin", "created_at", "email", "encrypted_password", "name", "salt", "updated_at") VALUES ('5580b5fb05a7f731064139cba53ee2e338459455550307d29f31c1b50fddc892', 'f', 'f', '2010-09-13 21:12:10.253568', 'mhartl@example.com', '1824c9e2d833d23c50df864d16f3ca9e468864cca2cb463c55cd1d0a8eed5c9f', 'Michael Hartl', '077db4dfcf5091ac88388a09421c9241e2ef8951c8521b08fcb687dd9ea7b34b', '2010-09-14 21:12:10.256094')
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE (created_at <= '2010-09-13 21:12:09.338912' and active = 'f')

試しに、以下のように、saveメソッドがtrueが戻ってくるのを待ってみましたが、結果は同じでした。

  it "should destroy" do
    user = User.create!(@attr)
    user.created_at = 1.day.ago
    if user.save
      User.expired.destroy_all.should == [@user]
    end
  end

解決策が見つからないので、2.days.agoでユーザを作成しテストを通しましたが...

解決策

(9/16追記)
結局、有効期限を引数で与えることにしました。

describe "expired users" do

  before(:each) do
    @expire_date = 1.day.ago
    @user = Factory(:user, :active => false, :created_at => @expire_date)
  end
 
  it "should exist" do
    User.should respond_to(:expired)
  end
 
  it "should destroy" do
    User.expired(@expire_date).destroy_all.should == [@user]
  end
end

そして、app/models/user.rbのexpiredスコープで引数を取れるように修正。

scope :expired, lambda { |expire_date|
  where(["created_at <= ? and active = ?", expire_date.nil? ? 1.day.ago : expire_date, false])
}