2012年12月3日月曜日

Arel scope の OR

http://dodemoyoiblog.blogspot.jp/2012/09/scope.html
上の記事でも書いたが、Arel の scope は便利だ。これは何度言っても言い足りない。

細かいことは元記事を読んでもらうとして、
scope にビジネスロジック上の重要概念を表現しておいて、クエリでは scope の組み合わせを用いることで、複雑なクエリでも極めて簡潔に表現することができる。

scope にビジネスロジックの概念を表現させて組み合わせることによって得られるメリットは、(1)個々の概念に明確に名前が付けられるため開発者間でのコミュニケーションがスムーズになる。DDD のユニバーサルランゲージと役割が近い。(2)ビジネスロジックという比較的変化しにくくアプリ全体で利用されるものをパックしているので再利用性が高い。(3)クエリ側の修正も scope 単位での追加削除によってなされることになるので、sql や AREL の where/join などを直書きするよりも間違いが起きにくい。where や join の直書きでは、複数の join, where などの組み合わせでひとつの役割を果たすことが多いがそれは字面を読んでもわからない。(4)ビジネスロジックそのものに修正が入った場合も、そのビジネスロジックがひとつの scope の中に閉じ込められて表現されていることが多いため、変更がスムーズ。

などだ。

そしてさらに嬉しいのが scope の merge だ。これは複数の scope をくっつけることができる。例えば


User.valid.merge( User.has_email )

のように書くと

User.valid.has_email

と同じ意味になる。これだとあまり旨みがわからないかもしれないが、merge の効果が高まるのは両者のクラスが異なるときだ。

User.valid.join(:emails).merge( Email.email_contains( str ) )
Admin.valid.join(:emails).merge( Email.email_contains( str ) )

このように、Email の scope を別のモデルのクエリに使いまわすことができる。
上の例のような単に like で検索するみたいな scope だと正直あまり意味はないが、複雑なビジネスロジックが scope で表現されているときこれは非常にありがたい。


あたりが参考になる。

しかし、残念なことがひとつある。merge は and が表現できるが、or が表現できない。ドキュメントには記載されているらしいが、使えない。

例えば

User.valid.join(:email, :tels).merge( Email.email_contains(email).or( Tel.tel_contains( tel )) )

みたいなことがしたい。だがまだ未実装である。

そこで、限定的ではあるが、以下のような書き方をすることで scope を再利用しつつ OR が使える。

User.valid.join(:email, :tels).merge( 
  "(#{Email.email_contains(email).where_values.join(' AND ')}) OR (#{Tel.tel_contains( tel ).where_values.join(' AND ')})"
)

え、きたならしい?そこは適当にメソッドを作って、例えば以下のコードで上と同じ動きをするようにすればよい。
User.valid.join(:email, :tels).merge( 
  are_or( 
    Email.email_contains(email),
    Tel.tel_contains( tel )
   )
 )

大事なことは概念が scope としてパックされてアプリ全体で使いまわされるということだ。

以上。





2012年11月24日土曜日

入力フォームの設定化

現在開発しているプロジェクトでは、入力フォームの設定を外部化している。

例えばhamlで入力フォームを書くと大体以下のようになるはずだ。
  = f.text_field :name, :class => "Name"

  = f.text_field :price, :class => "Price"
  = f.date_select :created_on

こう書く代わりに、hamlには
  - input :name
  - input :price
  - input :created_on
とだけ記述するようにしておき、

別ファイルのCSVに
name,text
price,money
created_on,calendar
のように書く。この組み合わせによって入力フォームが生成される。
実際には CSV には 他にもタグの属性を追記できたりするが、そういう細かい話はおいておく。

ここで大事なことは haml はあくまでも要素のレイアウト(配置)を定義するファイルであり、個々のファイルがどのような見た目、動きになるか、というのはCSVで管理する、ということだ。また、アプリケーション共通で担保すべき動きは helper に記述され、CSVで記述されるのはそのフォーム固有の設定のみとなる。

Struts の時代に戻ったのか、と思ってしまうかもしれない。このやり方が理想的なやり方だと主張するつもりは無いが、ある程度の旨みは感じている。もともと以下のようなことを考えてこの仕組みに決めた。大体目的は達成できている。
  • haml は出来る限り構造を捉えやすい状態に保ちたい。入力フォームの詳細が入ってくるのは好ましくない。erb ではどうでもいい html タグの海の中に入力フォームタグが点在している状況で、タグの親子関係がどうなっているのか調べるのが意外と大変だった。(特に一画面では収まらないような div など)
  • text_field などのhtmlタグにほぼ対応した"物理"タグ生成メソッドはできる限り使わずに、もっと高機能な"論理"タグを出力するhelperメソッドを定義して使うようにしたい。例えば日付の入力フォームはアプリ全体を通して同じ見た目、動きをするのが自然である。また、datepicker などの機能も何も考えずに設定されている状態にしたい。したがって text_field を直接呼び出すのではなく calendar というメソッドを作ってこれのみ使うようにする。
  • フォームの動きを修正するという作業と、フォームのレイアウトを修正するという作業が同時に発生することはない。したがって両者は分離されてそれぞれコンパクトに表現されている方が好ましい。
  • 上記のような高機能な論理タグを定義することで、設定ファイルも簡素化できる。物理タグを使っていたのでは設定ファイルも煩雑になって旨みが薄れる。
  • フォームの動きに関する情報が設定ファイルにコンパクトにまとまっているため、フォームの動きを修正するという作業が高速に行える。過去erbに物理タグを直接書いていた時代は膨大なタグの海から修正箇所をエディタの検索で探しだすという作業に神経をすり減らしていた。構造を捉えることの難しいファイルの修正は絶えずウォーリーを探し続けるようなもので、ただただ疲れる。
  • レイアウトに関する情報は haml にまとまっているので、配置の変更などもスムーズに行える。<tr> から </tr> まで範囲指定でカットして、目的の tr の挿入位置をエディタの検索で探しだして。。。という事がなくなる。html を中途半端にコピーしてしまって崩れるということも起こりづらい。
  • 各パーツの標準的な動きが論理タグとして helper にまとまっているため、カレンダーの動きが使いづらい、という場合に一箇所を修正すれば全体の動きを変えることができる。ユーザにとってフォームの動きが場所によって違うというのは結構なストレスになるのでこの点はとても重要だった。
以上。

2012年10月1日月曜日

model の decorator の話

最近の sapporo.rb などでは ActiveDecorator など、model の Decorator の話で少し盛り上がっているようだ。view に関わるコードをどう整理するか?という話について、共通見解が定まってきたということだろう。

rails の MVC に沿ってコードを書くと、view 回りがだんだんごちゃっとしてくる。scaffold のように単純に model の property を表示するような view なら良いが、STI のタイプに応じて表示内容を変えるとか、新規登録の場合と更新の場合で表示を変える、ユーザの権限に応じてどこまで情報を見せるか制御するなど、model のデータやセッションのデータに基づく条件分岐と html の描画が絡み合うような場合にこれをどこに書くのか?というのが問題になる。

伝統的な rails のレイヤーわけだと候補としては view, model, helper のいずれかになるだろう。この中のどれが適切か?

view にロジックを書くな、ということは昔から言われている。view のようなUIに最も近いところにモデルのロジックが入り込んでしまうと仕様変更に弱くなる。またデザイナーと分業しているときにメンテがしづらくなる。だから view にはできるだけコードは書かない。

では model に書くのか?これもあまりうまくない。まずmodelには html を描画するための十分な能力が無い。helper を include してしまうとよくわからないメソッドが大量に流入してくる。また、view に関する雑多なメソッドが model に入り込むとすぐに fat な model が出来上がる。あるプロパティでも画面によって微妙に表示の仕方が異なる、ということはよく起きるが(たとえばメールと画面では微妙に違う)、そういう些細な違いのメソッドが沢山modelに生えるというのは好ましくない。

では helper に書くのか?helper に書くのは良いアイデアのように見える。view に直接ロジックを書かないようにするための仕組みがhelperだ。ここしかない。実際小規模のアプリでは十分うまく働く。しかし、規模が大きくなるにつれて問題が出てくる。原因はhelperがフラットなためだ。このフラットな空間に様々な view 用の メソッドが生えてくると結局管理できなくなる。ある helper メソッドがどの view で使われているのか、いちいち grep するか?また helper のデフォルトの分け方が controller 単位なのも管理がしづらい原因でもある。view のメソッドは view 単位か model 単位で分けられるのが自然だと思う。

そこで出てきたのがこれらの Decorator だ。上であげた3つのレイヤーいずれとも異なる新しいレイヤーだ。Decorator は model をラップすることで表示専用のメソッドを追加することができる。ラップしているだけなので元のモデルの挙動は確保される。また、必要なときに必要なロジックが追加できる。ラッパーから helper へのアクセスを用意しておけばhtmlの描画もそれほど苦ではない。

この Decorator をどういう単位で作成するか、というところについては検討の余地がある。選択肢としては、

  • 一つの View につき一つの Decorator
  • 一つの model につき一つの Decorator
  • View x Model の組み合わせにつき一つの Decorator
最初のものはいわゆる Presenter パターン。ActiveDecoratorは二つ目のパターン。最後のものは Exhibit パターンという名前がある。ここの理解は少し怪しい。最後の一番細かい粒度で設定できるのが使い勝手としては一番良いと思う。view か model のどちらか選べと言われれば view にすると思う。ActiveDecorator のような、model 単位で Decorator 一つ、というのは結局 helper と大差なくなる。


2012年9月26日水曜日

haml のうまい使い方

1年ほど前から業務アプリの開発に haml を使っている。

世の haml の紹介では html って閉じタグが無いからすっきり書けるよ!とか.Hogeって書くだけで<div class="Hoge"> になるよ!簡単!みたいなレベルで紹介されているが、haml の真価はそんなところにはないと思っている。haml が本当にすごいと感じるのは、view を作るための簡易 DSL を定義できるところにあると考えている。理想的には、html タグを一切かかない view、ということになる。(もちろんそのような原理主義的なやり方はうまくいくはずがない)。

世の中で紹介されている、haml すげー!という解説はこんな感じ。

 %div.SearchForm
    名前
    %input{:type=>:text, :name => :name}
    生年月日
    %input{:type=>:text, :name => :birthday}

上げられる利点は

  • 閉じタグ書かなくていいよ
  • クラスとかIDが簡単に設定できるよ
という感じ。上に書いたようにこれらはさほど重要ではないというか、こういう書き方だとデザイナーが入りづらくなっただけで私が良いと思う haml のコードは

  • 見た目上我々が認知するブロック構造と haml の構造が良く対応している
  • 全てのタグ(haml行)は単なる checkbox や div よりもビジネスロジック上やUI上リッチな意味を持つ(論理タグと呼ぶ)
  • ビジネスロジックやUI上意味を持たない余計な html タグ(物理タグと呼ぶ)があまり現れない

適当にでっちあげた例だが、例えば以下のようなものだ。

- search_form do
  - left do
    - search_field :name
    - search_field :email
  - right do
    - search_field :birthday
    - search_field :user_type_id
  - buttons do
    - clear_button
    - csv_download_button
    - submit_button

この例では検索フォームを記述するときに、フォームの左側と右側にそれぞれ何を書くか。サブミットボタンの部分に何を書くか、という設定をする。それ以外の枠組みは search_form というメソッドが書いてしまう。

あるいは、タブ形式の入力画面は以下のようになる。

- tabs do
  - tab "基本情報" do
    - input :name
    - input :kana
    - input :email
  - tab "住所" do
    - input :zip_code
    - input :address

ラジオボタンの選択結果によって入力できる場所が切り替わる(入力できない場所が disable されるようなもの)を書くときは

- exclusive do
  - choice "メールで連絡を希望" do
    - input :email
  - choice "電話で連絡を希望" do
    - input :tel

のようにする。

このようにすることで記述量を劇的に減らすことができるし、一行当たりの意味の濃度が圧倒的に高いので理解もしやすい。UIの要素ごとにきれいにコードの共通化が行われているため、品質の一定化が容易だし、変更にも非常に強い。

ただ、ほかでよく指摘されているように、デザイナーさんとの協業は相性が悪い。業務アプリのような、ページのコンポーネントが限られていて、かつデザイン上後から手を加えるようなことが少ない場合に使いやすい。私はもっぱら業務アプリに使っている。twitter bootstrap と組み合わせると非常に効果的だ。

また同様に、この DSL が動きだすまでの最初の準備にはかなり手間がかかる。そのため短期のプロジェクトではあまりペイしない。ここも twitter bootstrap でうまく組み合わせることでプロジェクトをまたがった再利用ができるようになると思う。

個人的にはさらに cucumber の用語とすり合わせることでテストが簡単に書けるようになるかなぁと考えている。

model を物理レイヤーと論理レイヤーに分割する。

エンタープライズrailsという書籍に、モデルをphysical レイヤーと logical レイヤーに分けろ、というアドバイスがあった。この分け方が絶対かといわれるとよくわからないが、私も基本的にはこの考え方には賛成だ。

モデルのレイヤーを分けて考えるということはrailsのような密結合のフレームワークにおいて長期にわたってメンテナンスするコードを書くのであれば必須の事項になると思う。

どのようなレイヤーが必要になるかはアプリの複雑度によると思うが、最低限必須なレイヤーが上で書かれているphysical(DB層) とlogical(ビジネスロジック層) の二つのレイヤーだ。モデルのメソッドを physical と logical に分離し、controller から呼び出すのは基本的に logical 層のメソッドに限定する(厳密に守る必要はないと思うが)こととする。

例えばブログの記事を投稿する、というビジネスロジックがあったとする。内部的にはこれは Post というモデルのインスタンスを save するということになる。しかしこの save という単語はビジネスロジックの単語ではない。ビジネスロジックでは post だ。ビジネスロジック上の「投稿」という処理を表すのが post であり、そのDBレイヤーでの実現方法としてsaveがある。

def post
  save
end

大事なことは、全ての post は save であるかもしれないが、全ての save は post ではないということだ。つまりこれらは異なる概念であり、混同すべきではない。controller から save を直接呼ぶというのはこれを混同していることになる。

以前 before_save の弊害について書いたことがあるが、その問題の具体例がこれにあたる.。たとえば、記事の投稿をするときに記事データからタグを除去するなど正規化の処理をかけたいという要件があったとしよう。これを before_save で実現するというのは post と save を混同しているということに他ならない。全ての save が post 処理ではないのに、post 時のフックがすべての save 時に発動してしまう。このような予期しないフックが見えないところで動いていると予想しづらいバグを生む。バグが起きたのでフックをはずそうということになったはいいが、一度発動した状態で運用が続いていたものに対して、そのフックを本当に外しても大丈夫か、というのを考えるのは骨が折れる。

また別の面でも問題がある。一般的に、仕様変更はビジネスロジックの概念の単位で行われることが多い。「post時の処理にxxxを追加」という要求は出てきても、「save時の処理にxxxを追加」という要求はあまり出てこない。ビジネス上の要件はビジネス上の言葉で表現されるからだ。だからcontroller と model のインターフェース部分にビジネスロジックのレイヤーを設けることには変更に強くなるという点で非常に意味がある。




2012年9月23日日曜日

to_sql のうまい使い方(2)

前回の記事で、通常の ActiveRecord のクエリではパフォーマンスがよくないという場合に、うまくARELのよさを生かしつつ find_by_sql で高速なクエリを実行するための方法を紹介しました。
to_sql と find_by_sql でクエリを高速化する方法としてもうひとつ適用例があります。

本題に入る前に ActiveRecord が持っているあるパフォーマンス上の問題を紹介します。もしかすると最近の rails では改善されているのかもしれませんが、確認せずに書いてしまいます。その問題とは、joins を使ったテーブルに対しては eager loading が行われない、というものです。たとえば

users = User.joins(:emails).where("emails.address like '%@gmail.com'").includes(:emails)

のようにして、gmail のアカウントを持つユーザをとってきたとします。このクエリでは includes によって emails を eager loading するよう指示していることに注意してください。

さて、先頭のユーザが softbank のアドレスも持っているかどうかを調べたいとしましょう。

users.first.emails.any?{|e| e.address =~ /@softbank.ne.jp\Z/}

この時、users.first.emails にアクセスしたときにどうなるでしょうか?includes(:emails) を指定しているのだから eager loading された結果の emails  がすでに存在するはずで、ここではクエリは発生しないはずです。ですが残念ながらそうはなりません。ここで select * from emails where user_id = xxx というクエリが発生してしまいます。いわゆる1+n問題が発生してしまうのです。しかもこれを回避するうまい方法というのもありません。preload_association がまだ生きていたころに association を強制注射してみましたが、eager loading のクエリは実行されるものの、モデルはセットされずに結局 lazy load が発生しました。

この問題に対してはいつも以下のように対処しています。

まず、前回の記事で説明したような方法を使って、select + to_sql でモデルの id のみを取得します。

ids = User.connection.select_rows( User.joins(:emails).where(...).select("users.id").to_sql )

このように、connection.select_rows を使うと id の配列が得られます。(正確には配列の配列だが)。前回は find_by_sql を使いましたが、モデルが不要な場合、select_rows などの下位APIを使って配列など生データのみ取得したほうがはるかに高速です。

そしてその後idだけをつかって

User.where(:id => ids).includes(:emails)

とするときれいなモデルが得られます。

前の記事で、ビジネスロジック上意味のある絞り込み条件(activatedなど)を定義することで再利用性が高まるということを書きましたが、これはここでも適用されます。そのような scope は最初の id を引っ張るところにうまく使い回しできて、実際に必要なデータ(カラムや関連)を取ってくるところは
二つ目のSQLで調整する、という役割分担になります。


2012年9月21日金曜日

resque と delayed_job

resque と delayed_job の違いについての質問に対する回答がなかなかよかった。

http://qa.atmarkit.co.jp/q/2406

個人的には、resque って長いトランザクションの中からキューを突っ込んだ場合、トランザクションが完了するまえに resque のワーカーが動き出してしまい、そのタイミングではまだコミットされてないからトランザクションの中で加えられた変更が見えてなくておかしな挙動になる、という問題が結構深刻で使いづらいんだけど、何か対策あるのかな?

delayed_job はキューが mysql なのでその心配はない。ということで delayed_job を使っている。


しっかし↑このサイト stack overflow のパクリ丸出しだなー。