Upload
shozo-hatta
View
1.004
Download
1
Embed Size (px)
Citation preview
Crafting Rails 4 Applications
第2章Active Modelを使って
モデルを作る
目標Mail Formプラグインを作ってActive Modelを勉強する• ActiveModel::AttributeMethods
• API準拠• ActiveModel::Conversion
• ActiveModel::Naming
• ActiveModel::Translation
• ActiveModel::Validations
• ActiveModel::Callbacks
• そして最後に…
以下のプラグインを作ってみようPost リクエストから送信されたハッシュを受け取り、バリデーションを行ってから、特定のメールアドレスに送信する
まずは一発
attributes()
最初にattributes()メソッドを作ってみる
(動作: Mail Formオブジェクトに含める属性を指定する)
ActiveModel::AttributeMethodsMail Formオブジェクトに含める属性を指定する
テスト:
attributes()この時点ではMailForm::Baseがないのでbundle exec rake testすると当然失敗する
テストが通るようにするためにMailForm::Baseを実装
属性の作成はattr_accessor()に委譲(delegate)している
attributes()
attributes()autoload()とは
• MailForm::Baseという定数が最初に参照されるまではmail_form/base.rbが読み込まない
• 起動を早めるためによく使われるテクニック
autoload()を追加することでテストがパスする
ActiveModel::AttributeMethods
ActiveModel::AttributeMethodsは、定義済みの属性を追いかけて共通の動作を追加する
ここではclear_というprefixにマッチするメソッドを定義してみる(ActiveRecordでおなじみの、定義した属性に自動的にメソッドが追加される、あの動作を再現する)
まずはテストを作成
ActiveModel::AttributeMethods
clear_で始まる属性を定義し、それを使用してメソッドを定義する
ActiveModel::AttributeMethods
これでテストに通る
ActiveModel::AttributeMethods
attribute_method_prefix()をattribute_method_suffixに変えればsuffix
でも同じことができるname?()とemail?()にマッチさせる場合
attribute_method_affix()を使用すれば両方を同時に指定することもできる
秘密はmethod_missing()にあり
ActiveModel::AttributeMethodsは、動的なメソッドをキャッチするために内部でmethod_missing()を使用する
clear_name()やclear_email()のようなメソッドが明示的には定義されていないのに、属性を定義するだけで利用できるのはこのおかげ
そしてActiveModelは、clear_*をキャッチしたらclear_attributeを呼び出してくれる
method_missing()method_missing()は、メッセージの継承パスの最後のところでメッセージをキャッチする
(排水口に取り付けたストッキングみたいなもの?)
method_missing()はしばしば黒魔法にたとえられる
非常に強力だが、乱用すると流れを追いにくくなり、えらいことになるらしい
このあたりのことは「Metaprogramming Ruby」に詳しく載っている
method_missingActiveRecordではこのような動的なattribute methodが広く使われている
でも、Rails 4 で find_by_* 形式の動的な検索メソッドが非推奨になったのは、もしかしてやりすぎてしまったからなのかも。
Railscastより:
ぼそっとrubyのクラス定義でクラス名をファジーマッチできたらもっとシンプルにならないだろうか?
冗談です
Active Model APIに準拠する
自作モデルをActive Model APIに沿ったものにする:
(そうしないとcontrollerやviewで使えない)
実は ActiveModel::Lint::Testsですべて行える
Active Model APIに準拠する
APIに合致するまでエラーを出しまくる
Active Model APIに準拠する
以下のエラーを解決しよう
以下を定義すればよい
でもこれをもろに実装する必要はない
ActiveModel::Conversion
ActiveModel::Conversionを使用すれば、to_model()だけでなくto_key()やto_param()、to_partial_path()などの必須メソッドを実装できる
• to_key() : モデルを指定するキーの配列を返す。viewのdom_id()で使用され、dom_id()はRailsで広く使われている
• to_param() : ルーティングで使用され、モデルのユニークURL生成のために広く使われている
• to_partial_path() : ビューのrender()で使用される
ActiveModel::Conversion
これらのメソッドをカスタマイズすることもできる
to_param() : URLのidにタイトルを加えたい:
to_partial_path() : postでフォーマットを指定したいビューでこう書くより
オーバーライドしてから呼び出す方がスマートかつ高速(render呼び出しが1回で済むので)
ActiveModel::Conversion
ActiveModel::Conversionをインクルードするだけでよい
これでエラーが3つに減った
ActiveModel::Namingmodel_nameのエラーを減らすには、ActiveModel::Namingをエクステンド
これを追加すると、human()やsingular()などのモデル名活用が行われる
ActiveModel::Translation
i18n対応するにはActiveModel::Translationをエクステンド
* i18nバックエンドが確実にリロードされるようにbegin…ensure…endで囲んでいる
ActiveModel::Validation
以下のエラーを解消するためにActiveModel::Validationをインクルード
persisted?()今回、以下のエラーについては自前で実装が必要
persisted?()このmail_formで永続性は不要なのでfalseを返すようにする(※protectedの下に書かないこと)
これでテストはすべてパスする
Formを配信する次はdeliver()メソッドを実装してみる• モデルのemail属性に保存されているメアドに送信する
でテスト
Formを配信する実装(MailForm::Notifierを作成)
ActionMailer::Baseを継承して実装
contact()はMailFormクラスではなく子クラスのメールデータ(:to, :from, :subject)を返す(配信先をカスタマイズするのにNotifierクラスをいじる必要がない
Formを配信するappend_view_path()はプラグインフォルダ内にlib/viewsフォルダを追加しテンプレート検索の対象とする
以下を追加
Formを配信する準備が整ったのでサンプルクラスにメソッドを追加してみる
メイラーにビューテンプレートがないのでこの時点では失敗する
ビューテンプレートを追加してみる
Formを配信するattrubete_names()というclass_attributeを定義して属性のリストを取得できる
• attributes()を呼ぶたびに更新される• 継承時に自動的に動作する
これでテストに通る
capybaraで結合テスト(略)
validationvalidates_presence_of() の動作:
以下の呼び出しは基本的にどれも同じ
Railsは:presenceキーを PresenceValidateに変換し、現在のクラスにPresenceValidatorという名前の定数があるかどうかを調べる:
validationRubyでは定数の探索はすべての先祖クラスに対して行われるので、これは動作する
validationスパム判定用のabsence validatorを作ってみる(ボットにしか見えないnicknameフィールドをわざとフォームに作っておき、そこに書き込みがあればスパムとみなす)
まずはテスト
AbsenceValidatorクラスを定義 (EachValidatorを継承)
validationMailForm::Baseでインクルードする
MailForm::ValidatorsはMailForm::Baseの先祖チェインに追加される
それにより、:absenceをvalidates()のキーとして与えると、AbsenceValidator定数の値が探索され、MailForm::Validatorsの中を検索、初期化が行われる(PresenceValidatorのときと同様)
あとはautoloadすればよい
ActiveModel::Callbacks
最後に、deliver()メソッドにbeforeとafterを追加してみよう
まずはfixture
evaluated_callbacks()メソッドを定義
ActiveModel::Callbacks
callbackが評価されていることを確認するテスト
実装 (ActiveModel::Callbacksをextendしてコールバックを定義)
ActiveModel::Callbacks
これでテストは通る
ActiveModel::Modelここまでは勉強のために個別に実装を行ってきたが、実はActive
Modelに準拠するのは非常に簡単:
ActiveModel::Modelをインクルードするだけでいい
これだけで、Lintテストはすべてパスする
ActiveModel::Modelその実装(これまでやってきたことを集約しただけ)
おまけこの章と同じコンセプトで作られたmail_form gemがあるhttps://github.com/plataformatec/mail_form
おつかれさまでした