モデルの構築とテスト
さてここからは、テストファースト風味でモデルを構築していきます。
まず、クライアントとカウンセラーをどうモデル化するか。
クライアントはユーザで、カウンセラーは予約するリソースです。だから、厳密にモデリングすると、両者は無関係な別個のエンティティになるような気もします。しかし、一方で、どちらもログインしてシステムにアクセスしてくる主体(=ユーザ)でもあります。
そこで、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のソースを本体側にコピーして書き直せば、汚ないけどなんとかなるという読み)