assert_differenceとは

後続するblockの前後での差異を調べるassertかな.詳しく書いているDocumentがなかなか見つからないんだけど,has_many :through: Getting arbitrary with assert_differenceによると,下記のcodeがある場合,do〜end前と後とでUserオブジェクトの個数(:count)の差が1(1)であれば通るんだそうな.

def test_create_user
  login = "bob"
  name = "Bob Dobbs"
  assert_difference(User, :count, 1) do
    bob = User.create!(:login => login, :name => name)
    assert_equal login, bob.login
    assert_equal name, bob.name
  end
end

確かに便利そうではある.

act_as_authenticated

以下を参考に導入してみた.

Installation

プラグインのインストール

% ruby script/plugin discover
% ruby script/plugin install acts_as_authenticated

UserモデルとAccountコントローラの作成

% ruby script/generate authenticated user account
% vim config/database.yml
% rake db:migrate

authenticated_mailerのセットアップ

対応するモデル(User)を指定.

% ruby script/generate authenticated_mailer user

メールサーバの指定などを追加

vim config/environments/development.rb
ActionMailer::Base.smtp_settings = {
  :address => 'smtp server',
  :port => 25,
  :user_name => 'username',
  :password => 'password',
  :authentication => :plain
}

app/models/user_notifier.rb -> setup_emailメソッド内のインスタンス変数を変更

@from
@subject
@body[:user]

Userモデルの変更

Schemaの変更
% ruby script/generate migration AddActivationToUser
% vim db/migrate/002_add_activation_to_user.rb
class AddActivationToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :activation_code, :string, :limit => 40
    add_column :users, :activated_at, :datetime
  end

  def self.down
    remove_column :users, :activation_code
    remove_column :users, :activated_at
  end
end
% rake db:migrate
Userモデルの変更

下記メソッドを追加

class User < ActiveRecord::Base
  before_create :make_activation_code

  # loginと非暗号のパスワードから認証し、userかnilを返す
  def self.authenticate(login, password)
    # activated_atがnullではない
    u = find(:first, :conditions => ['login = ? and activated_at IS NOT NULL', login])
    u && u.authenticated?(password) ? u : nil
  end

  # activateしてインスタンス変数を設定
  def activate
    @activated = true
    update_attributes(:activated_at => Time.now.utc, :activation_code => nil)
  end

  # インスタンス変数を返す
  def recently_activated?
    @activated
  end

  protected
  # 有効化コードを生成する
  def make_activation_code
    self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
  end
end

Accountコントローラの修正

class AccountController < ApplicationController
  def signup
    # Activation前まで
    @user = User.new(params[:user])
    return unless request.post?
    @user.save!
    #self.current_user = @user
    #redirect_back_or_default(:controller => '/account', :action => 'index')
    # Activationへ飛ばす
    redirect_to(:action => 'signup_notification')
    flash[:notice] = "Thanks for signing up!"
  rescue ActiveRecord::RecordInvalid
    render :action => 'signup'
  end

  # Notification用
  def signup_notification
  end

  # ユーザをActivateする
  def activate
    @user = User.find_by_activation_code(param[:id])
    if @user and @user.activate
      self.current_user = @user
      flash[:notice] = "Your account has been activated."
    end
  end
end
23:40 追記:Observerの設定

config/enviromnent.rbに下記を追加

config.active_record.observers = :user_observer

各種Viewの作成

  • app/views/user_notifier/signup_notification.rhtml
アカウントが作成されました.

  ユーザ名 : <%= @user.login %>
  パスワード: <%= @user.password %>

アカウントをアクティベートするために,下記URLにアクセスしてください.

  <%= @url %>
  • app/views/user_notifier/activation.rhtml
<%= @user.login %>さん

ユーザ登録が完了しました.

  <%= @url %>
  • app/views/account/signup_notification.rhtml
アクティベートの為のメールを送信しました.
メールの内容に従ってアクティベーションをしてください.
  • app/views/account/activate.rhtml
ようこそ <%= self.current_user.login %> さん!
ユーザー登録が完了しました。

確認

メールが届かないorz

23:40 追記:Observerを追加

したがまだメールが....orz

注意事項

認証が必要なコントローラに以下を追加.

before_filter :login_required