モデルの構築とテスト

さてここからは、テストファースト風味でモデルを構築していきます。

まず、クライアントとカウンセラーをどうモデル化するか。

クライアントはユーザで、カウンセラーは予約するリソースです。だから、厳密にモデリングすると、両者は無関係な別個のエンティティになるような気もします。しかし、一方で、どちらもログインしてシステムにアクセスしてくる主体(=ユーザ)でもあります。

そこで、Railsの「単一テーブル継承」という機能を使うことにします。

UserというLogineEngineで定義されたベースクラスから、CounselorとClientというクラスを継承します。そして、CounselorとClientが保持する属性値は、usersというデータベースのテーブル上に保存されます。

まず、Userのモデル定義をアプリ本体のモデル定義にコピー。

$ cd coreserve
$ cp vendor/plugins/login_engine/app/models/user.rb app/models

Engineという拡張の仕組みでは、このように、本体のディレクトリにモデルの定義が存在すると、自動的にそれが使用されるようです。

その定義に、CouselorとClientを追加

class User < ActiveRecord::Base
  include LoginEngine::AuthenticatedUser

  # all logic has been moved into login_engine/lib/login_engine/authenticated_user.rb

end

class Counselor < User
  def counselor?
    true
  end
end

class Client < User
  def counselor?
    false
  end
end

それから、テーブル定義にtypeというカラムを追加します。

class InitialSchema < ActiveRecord::Migration
  def self.up
    create_table LoginEngine.config(:user_table), :force => true do |t|
      t.column "type", :string, :limit => 20, :null => false
      .....
    end
  end
  .....
end

script/consoleで軽く動作確認してみます。

$ script/console
Loading development environment.
>> Counselor.create(:login=>'co1', :email=>'a@a.com')
=> #<Counselor:0xb736cbe0 @errors=#<ActiveRecord::Errors:0xb76b7fc0 @errors={}, @base=#<Counselor:0xb736cbe0 ...>>, @attributes={"salt"=>"", "delete_after"=>nil, "updated_at"=>Tue May 30 09:47:23 JST 2006, "security_token"=>nil, "role"=>nil, "type"=>"Counselor", "id"=>1, "lastname"=>nil, "firstname"=>nil, "deleted"=>0, "token_expiry"=>nil, "verified"=>0, "logged_in_at"=>nil, "salted_password"=>"", "login"=>"co1", "created_at"=>Tue May 30 09:47:23 JST 2006, "email"=>"a@a.com"}, @new_password=false, @new_record=false>
>> Client.create(:login=>'cl1', :email=>'a@a.com')
=> #<Client:0xb7692798 @errors=#<ActiveRecord::Errors:0xb768924c @errors={}, @base=#<Client:0xb7692798 ...>>, @attributes={"salt"=>"", "delete_after"=>nil, "updated_at"=>Tue May 30 09:47:36 JST 2006, "security_token"=>nil, "role"=>nil, "type"=>"Client", "id"=>2, "lastname"=>nil, "firstname"=>nil, "deleted"=>0, "token_expiry"=>nil, "verified"=>0, "logged_in_at"=>nil, "salted_password"=>"", "login"=>"cl1", "created_at"=>Tue May 30 09:47:36 JST 2006, "email"=>"a@a.com"}, @new_password=false, @new_record=false>
>> co = User.find_first(['login = ?', 'co1'])
=> #<Counselor:0xb76646a4 @attributes={"salt"=>"", "delete_after"=>nil, "updated_at"=>"2006-05-30 09:47:23", "security_token"=>nil, "role"=>nil, "type"=>"Counselor", "lastname"=>nil, "firstname"=>nil, "id"=>"1", "deleted"=>"0", "token_expiry"=>nil, "verified"=>"0", "logged_in_at"=>nil, "salted_password"=>"", "login"=>"co1", "created_at"=>"2006-05-30 09:47:23", "email"=>"a@a.com"}>
>> co.counselor?
=> true
>> cl = User.find_first(['login = ?', 'cl1'])
=> #<Client:0xb76ae434 @attributes={"salt"=>"", "delete_after"=>nil, "updated_at"=>"2006-05-30 09:47:36", "security_token"=>nil, "role"=>nil, "type"=>"Client", "lastname"=>nil, "firstname"=>nil, "id"=>"2", "deleted"=>"0", "token_expiry"=>nil, "verified"=>"0", "logged_in_at"=>nil, "salted_password"=>"", "login"=>"cl1", "created_at"=>"2006-05-30 09:47:36", "email"=>"a@a.com"}>
>> cl.counselor?
=> false
>>
mysql> create database coreserve_test ;
Query OK, 1 row affected (0.00 sec)

mysql>  grant all on coreserve_test.* to coreserve@localhost identified by '**********' ;
Query OK, 0 rows affected (0.00 sec)

うまく行っているようなので、単体テスト環境を構築します。

まず、config/database.ymlにtest用パラメータを追加

test:
  adapter: mysql
  database: coreserve_test
  username: coreserve
  password: *********
  host: localhost
  socket: /var/run/mysqld/mysqld.sock

テスト用データベースにmigrateでテーブルを定義します。

$ RAILSENV=test rake migrate VERSION=0 ;rake migrate --trace

LoginEngineのテストスクリプトと関連するソースを本体側にコピーします。

$ cp vendor/plugins/login_engine/test/unit/user_test.rb test/unit  
$ cp vendor/plugins/login_engine/test/fixtures/users.yml test/fixtures
$ cp vendor/plugins/login_engine/test/mocks/* test/mocks/

コピーした単体テストをそのまま実行します。

$ rake test_units
(in /home/tnaka/coreserve)
/usr/bin/ruby1.8 -Ilib:test "/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader.rb" "test/unit/user_test.rb"
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader
Started
.......
Finished in 0.219067 seconds.

7 tests, 29 assertions, 0 failures, 0 errors

test/unit/user_test.rbに以下のメソッドを追加します。

  def test_create_co
    u = Counselor.new
    u.login = "co1"
    u.change_password("bobs_secure_password")
    u.email = "co1@email.com"

    assert u.save

    co = User.find_first(['login = ?', 'co1'])
    assert co
    assert co.counselor?
  end

  def test_create_cl
    u = Client.new
    u.login = "cl1"
    u.change_password("bobs_secure_password")
    u.email = "cl1@email.com"

    assert u.save

    cl = User.find_first(['login = ?', 'cl1'])
    assert cl
    assert cl.counselor? # should fail
  end

追加したテストが実行されているか確認する為に、最後の一行だけわざと失敗するように書きました。

$ rake test_units
(in /home/tnaka/coreserve)
/usr/bin/ruby1.8 -Ilib:test "/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader.rb" "test/unit/user_test.rb"
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader
Started
....F....
Finished in 0.226922 seconds.

  1) Failure:
test_create_cl(UserTest) [./test/unit/user_test.rb:137]:
<false> is not true.

9 tests, 35 assertions, 1 failures, 0 errors
rake aborted!
Command failed with status (1): [/usr/bin/ruby1.8 -Ilib:test "/usr/lib/ruby...]

うまく失敗したので、その行を直して、再実行して確認しました。

LoginEngineのテストには、fixtureやmock等、テストの為のRailsの技がいろいろ使われているので、必要に応じて、これを実行たり書き直したりしながら調査していく予定です。

LoginEngineと追加したクラスとの関連はいろいろ不安な所があります(ベースクラスのUserでなく、継承したClientやCouselorのオブジェクトで認証が正しくできるのかどうか、セッション上に意図したオブジェクトが構築されるのか等)が、この問題は、後で考えることにします。(最悪の場合、LoginEngineのソースを本体側にコピーして書き直せば、汚ないけどなんとかなるという読み)