2011年8月8日月曜日

Rails3を初歩から学ぶ#4 モデル向けのテストをつくる

前々回Userモデルを作成したので、このモデルにバリデーションを追加しましょう。
Userモデルで必要な必要な検証を下表に示します。
idinteger-
namestring必須、一意,50文字以内
★passwordstring必須,6文字以上40文字以内
encrypted_passwordstring
saltstring-
adminboolean-
created_atdatetime-
updated_atdatetime-
★印をつけた「password」だけは例外で実際のデータベースには格納しません。passwordを元に暗号化した値を「encrypted_password」としてDBに格納します。
ただしUserオブジェクト生成時では必須なので必須指定としています。
この辺りの事情は前回の解説をみてください。

というわけでまずはこの「name」と「password」に対する検証がちゃんと動作しているかを確認するテストコードを作成します。
モデル生成時に「spec/model/user_spec.rb」というファイルが出来ているのでこれを日以下のように編集します。

# -*- coding: utf-8 -*-
require 'spec_helper'
describe User do
  before(:each) do
    @attr = {
      :name => "hogetarou",
      :password => "foobar",
      :password_confirmation => "foobar",
    }
  end
  it "名前がないと検証NGであること" do
    user = User.new(@attr.merge(:name => ""))
    user.should_not be_valid
  end
  it "名前が50文字を超えると検証NGであること" do
    long_name = "a"* 51
    user = User.new(@attr.merge(:name => long_name))
    user.should_not be_valid
  end
  it "パスワードがないと検証NGであること" do
    user = User.new(@attr.merge(:password => "", :password_confirmation => ""))
    user.should_not be_valid
  end
  it "パスワードと確認用が一致しないと検証NGであること" do
    user = User.new(@attr.merge(:password_confirmation => "invalid"))
    user.should_not be_valid
  end
  it "パスワードが6文字未満だと検証NGであること" do
    short = "a" * 5
    user = User.new(@attr.merge(:password => short, :password_confirmation => short))
    user.should_not be_valid
  end
  it "パスワードが41文字以上だと検証NGであること" do
    long = "a" * 41
    user = User.new(@attr.merge(:password => long, :password_confirmation => long))
    user.should_not be_valid
  end
  it "全て満たしたとき検証OKであること" do
    user = User.new(@attr)
    user.should be_valid
  end
  it "ユーザー名が重複したらダメ" do
    User.create!(@attr)
    user = User.new(@attr)
    user.should_not be_valid
  end
end
最初の「before」で正しいデータを用意しておいて、各検証コードで「merge」メソッドを使って検証対象のフィールドをいじりながら検証しています。
rspec spec/user_spec.rb
と実行すると全てNGで返ってきます。
ちなみに「:password_confirmationなんて無いぞ!」というエラーばかりだと思います。
今はこれでOK。
ではモデルの方を作ってみましょう。
「app/model/user.rb」を以下のように編集します。

class User < ActiveRecord::Base
  attr_accessor  :password
  attr_accessible :name, :password, :password_confirmation
  validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 50 }
  validates :password, :presence => true, :confirmation => true, :length => { :within => 6..40}
end
まず1行目の「attr_accessor」ですが、これはRailsではなくRubyの仕様で、渡したパラメータのgetterとsetterを生成してくれるメソッドです。
モデル生成時に指定したフィールドはActiveModelが自動で生成してくれますが、passwordフィールドは生成していません(何度も言いますがDBに格納するのは暗号化した「encrypted_password」です)。なのでここでpasswordというフィールドを定義しているわけです。

次のattr_accessibleは指定した以外のフィールドを保護するメソッドです。
例えば前々回作成したモデルには管理者を示す「admin」フィールドが存在しますが、ユーザーインターフェース上に「admin」を示す入力項目が無かったとしても、悪意あるユーザーがPOSTメソッドで「admin=true」としたパラメータを送りつけて来た場合に、無条件に「update_attributes」メソッドなどでUserオブジェクトを更新してしまうと、そのユーザーに管理者権限を与えてしまうことになります。
attr_accessibleではコンストラクタや「update_attributes」メソッドで更新可能なフィールドを指定し、指定されなかったフィールドについては更新を許可しません。
ちょっと確認してみましょう。
「rails console --sandbox」としてコンソールを起動してみます。
rails console --sandbox

>> user = User.new(:name => "hogehoge", :password => "foobar", :password_confirmation => "foobar", :admin => true)
=> #
このように「:admin => true」と指定しているのに結果は「admin: false」となっていることからちゃんと保護されていることが分かります。
「attr_accessible」と似たものに「attr_protected」があり、こちらは逆に指定したフィールドだけを保護するメソッドです。
長期的にはフィールドが増加したときに「指定し忘れ」が起きてしまうことを考えると「attr_accessible」にしておいた方が安全でしょう。

以降は「validates」メソッドで必要な検証を定義しています。
「password」に対して「:confirmation => true」としています。
こうすることで「password_confirmation」というフィールドが自動で付加されて、「password」と一致していることをチェックしてくれます。

さて、user.rbを保存したらもう一度RSpecを実行してみましょう。
今度は全て成功するハズです。

0 件のコメント:

コメントを投稿