Episode今回のエピソードでは、presenterをゼロから作ります。読む]で扱ったものです。このアプリケーションにはユーザプロファイルのページがあり、アバター画像と一緒にユーザが入力した情報が表示されています。ユーザはすべてのフィールドに情報を入力しなくてもいいので、このページには代わりにデフォルト値を表示する機能が必要です。MrMysteryさんという、ユーザ名しか入力してないユーザの例を見ることができます。デフォルト値を扱わなくてはいけないということは、ページのビューコードにたくさんの複雑なロジックが含まれるという意味です。</dl></div>このページはいくつかのif文が含まれています。それぞれがあるフィールドに対して入力された値かデフォルト値のいずれを表示させるかのロジックを処理しています。このロジックはすべてビュー関係なので、presenterクラスを利用するのがいいでしょう。それによってビューのコードを見違えるほどきれいに整理することができます。presenterはビューとモデルの両方を理解しているクラスで、複雑なビューロジックを処理する優れたオブジェクト指向的な方法です。今回のアプリケーションでこれを使用して、ユーザプロファイルのページをきれいに整理します。最初のpresenterを書くpresenterはシンプルなRubyクラスです。アプリケーション内のpresenterを一緒にまとめておくために、アプリケーションのappディレクトリの下に新たにpresentersディレクトリを作成し、そこにuser_presenter.rbファイルを置きます。古いバージョンのRailsでは/config/application.rbファイルの中でアプリケーションのconfig.autoload_pathsに新しいディレクトリを追加する必要がありました。3.1ではサーバを再起動しさえすれば、この作業は必要なくなりました。新しいディレクトリはサーバ起動時に自動的に認識されます。presenterは処理対象のモデルとビューを知っている必要があるので、それらをinitializeメソッドに渡してインスタンス変数に割り当てます。最初にどこかで新しいUserPresenterクラスをインスタンス化する必要があります。Draperやその他のpresenterライブラリでは、これをコントローラアクションでおこなうのが一般的です。コントローラはpresenterを意識するべきではないという議論もあり、ここではそのアプローチはとりません。コントローラを修正するのではなく、presenterインスタンスを生成するヘルパーメソッドを新規に作成します。このメソッドはpresentという名前で、presenterを作成したい対象のモデルと、presenterオブジェクトを返すブロックをとります。ビューテンプレートのコードをすべてブロックの中に置いて、presenterがビューからアクセス可能になるようにします。%>presentメソッドをApplicationHelper内に書きます。モデルオブジェクトに加えて、オプションでクラス変数を指定して、使われるpresenterクラスをカスタマイズします。クラスが指定されない場合、オブジェクトのクラス名の後ろにPresenterをつけたものをベースにクラスを見つけます。今回の場合、名前はUserPresenterになります。その文字列に対してconstantizeを呼び出して、クラス定数を取得します。presenterクラスができたので、klass.newを呼び出してそれをインスタンス化し、モデルobjectとselfを渡します。selfは我々がアクセスしたいヘルパーメソッドを持つテンプレートメソッドです。ブロックがメソッドに渡されたらpresenterをyieldして、最後にそれを返します。endendpresentメソッドによって、いずれのテンプレートからでもすべてのオブジェクトのpresenterに簡単にアクセスできるようになります。これで、テンプレートからpresenterにコードを移動し始めることができます。アバターを表示するコードから始めます。%>これをUserPresenterに新たに作るavatarの呼び出しと置き換えます。%>ビューから取ったロジックをこの新しいメソッドに貼り付けます。@user.urlendpresenterから呼び出すヘルパーメソッドはすべてテンプレートを介して呼び出される必要があります。どこでも@templateを使うのではなく、Draperの手法にならってhメソッドを用いて、テンプレートを返すhメソッドをpresenterに追加します。その上でそれをlink_to_ifとimage_tagの各メソッドと合わせて使用して、テンプレートを介して呼ばれるようにします。コピーしたコードはまた、新規に作成したヘルパーメソッドのavatar_nameを呼び出します。これはUsersHelperで定義されています。endendpresenterにある同じモデルオブジェクトを引数にとるヘルパーメソッドがある場合は、それをpresenterに移動するのが理にかなっているので、avatar_nameをUsersHelperからUsersPresenterに移動してプライベートに変更します。presenterにはすでに現在のユーザがあるので、user引数は削除してその代わりに@userインスタンス変数を呼び出します。endendユーザプロファイルのページをリロードすると前とまったく同じように表示されるので、presenterは正しく動作しているようです。基本のpresenterを作成するpresenterを使用するアプリケーションはおそらくそれを複数持つので、それらの振る舞いを一般化するのがいいでしょう。作成するpresenterはすべて同じinitalizeとhの各メソッドを持つので、これらを基本クラスに移してpresenterから継承させることにします。新たに作成する基本クラスでinitializeに渡されるモデル名をより汎用的な名称、たとえばobject、に変更します。これによってUserPresenterで直接userオブジェクトを参照することができなくなります。@objectを呼び出すこともできるのですが、その代わりにBasePresenter内にnameを引数にとるpresentsという名前のクラスメソッドを作成します。このメソッドは、@objectに保持されたモデルを返す、そのモデルと同じ名前のメソッドを定義します。endendUserPresenterとその他の作成するpresenterはすべてこの新しいクラスから継承できるようになりました。userを返すuserメソッドを利用できるようになるので、@userの呼び出しをすべてuserに置き換えます。endendビューの残りの部分を整理するテンプレートは多少きれいになりましたが、まだ整理できる部分はたくさんあります。これをおこなうためにとるステップは、Draperのエピソードで採用した方法にとても似ています。具体的な方法を見たい場合は、動画を見るか記事を読むことができます。これらの変更の実際の作業は省略して、結果のみを示します。重いロジック部分をpresenterに移動して、テンプレートはずっときれいに整理されました。%>presenterのコードを下に示します。ページのロジックをすべて処理してくれるいくつかの短いメソッドだけになりました。endendpresenterに対しておこなった修正について、何点か補足します。ファイルの最初あたりでdelegateでusernameメソッドをUserクラスに委譲しています。Userモデルのusernameは変更する必要がないので、そこから直接取得できます。テンプレートはRedclothを用いてユーザの履歴を返します。これは他のpresenterで使うと便利なので、BasePresenterにmarkdownメソッドを作成し、どのpresenterのMarkdownコードも簡単に返せるようにしました。そこでUserPresenterのこのメソッドを呼び出すことができます。given(情報なし)」を表示させます。spanにデフォルトの値を返します。このアプローチは、このように単純なHTMLではうまくいきますが、より複雑なマークアップを表示したい場合は、部分テンプレート(partial)を作成してそれを表示させるか、Markabyのようなマークアップ言語を利用するなどの方法がいいでしょう。先にhメソッドを定義して、ヘルパーメソッドにアクセスできるようにしました。これを望まない場合、代替案としてmethod_missingを設定してすべての不明なメソッドをテンプレートに委譲するという方法があります。これはBasePresenter内に簡単に設定して、すべてのpresenterで機能させることができます。&block)endこれによって、presenterが理解できないものはすべてテンプレートに送られ、image_tagなどのヘルパーメソッドを呼び出すときはhメソッドを介するのではなく直接呼び出します。コントローラからpresenterにアクセスするある時点では、コントローラ層からpresenterにアクセスしなくてはいけないという状況が発生すると思います。UserControllerのshowアクションで使用して、次のようなコードでJSONを返したいという場合があるかもしれません。present(@user).to_jsonendpresentメソッドはヘルパーメソッドではないのでこれはうまくいきません。アプリケーションのApplicationControllerにコントローラ用のpresentメソッドを作成することができます。このメソッドのトリックは、presenterをインスタンス化するときにselfの代わりにview_contentを呼び出すという点で、これはそれがビューを返すテンプレートオブジェクトだからです。読む]で取り上げました)を使用してハイレベルな統合テストを行うべきですが、presenterを利用することの利点のひとつは、より低いレベルでビューロジックをテストできるという点です。これをこのあと実際にデモしていきますが、最初はTest::Unitで、次にRSpecでおこないます。Test::Unitによるpresenterのテストを書くために、/test/unitディレクトリの下に新規にpresentersディレクトリを作成し、そこにuser_presenter_test.rbファイルを追加します。このファイル内に作成するUserPresenterTestクラスは、通常のActiveSupport::TestCaseを継承するのではなく、ActionView::TestCaseを継承します。これによって、ビューにアクセスできるようになりそれをpresenterに渡すことができます。これを簡単なテストでデモします。given」というテキストが返されると仮定(assert)します。新規にUserPresenterを作成し、新しいUserとビューテンプレートを渡します。テストはActionView::TestCaseを継承するので、現在のビューテンプレートを保持するview変数にアクセスできます。Given」というテキストが含まれていると仮定します。testでテストを実行すると成功します。skipsテストからpresenterにアクセスできるということは、テスト駆動開発の手法を用いてpresenterを書けることを意味します。失敗するpresenterのテストから始めて、テストが成功するようにpresenterを修正していきます。presenterをRSpecを用いてテストするには、まず/specの下にpresentersディレクトリを作成し、そこにuser_presenter_spec.rbファイルを追加します。先ほどのテストと同じことをテストするspecを書きます。presenterのspecを書くときの鍵は、ActionView::TestCase::Behaviorをインクルードするという点です。これによって、テンプレートを含むview変数を使用できます。これはTest::Unitを利用しているときにActionView::TestCaseを継承する場合と同じです。spec自体は、先ほどのテストととても似ています。再度新しいpresenterを作成し、websiteメソッドが正しいテキストを返すかどうかチェックします。specを実行すると1つのspecが成功するはずで、実際その通りの結果が表示されます。ブロックを修正します。specにそれを追加する必要がなくなります。omitted.endpresenterをテストするときに気をつけなくてはいけないことがひとつあります。presenterに渡すviewオブジェクトはすべてのヘルパーメソッドにアクセスできますが、例外としてコントローラで定義されたものにはアクセスできません。コントローラ内にhelper_methodを用いてヘルパーメソッドとして指定したcurrent_userメソッドを持っていても、テストでそれをviewオブジェクトから呼び出すことはできません。代わりにこのメソッドをスタブ化して、求める値を返すようにする必要があります。通常current_userメソッドはセッションやクッキーの値に依存するため、いずれにしろここはスタブ化するのがいいアイデアでしょう。presenterをゼロから作る今回のエピソードは以上で終わりです。必要な作業の内容を見ることで十分な知識を得られたと思いますので、自分でカスタムのソリューションを書くべきか、Draperのようなgemを利用するべきか、判断することができるでしょう。どちらを選ぶにしても、アプリケーションのビューが複雑なロジックを持っている場合は、presenterを利用してロジックをリファクタリングしてビューをきれいに保つことをぜひ検討してください。