2013年7月5日金曜日

trigger ベースの履歴

railsを使って色々なアプリを作っているが、どのアプリでもmysqlで全てのテーブルの変更履歴を取るようにしている。なにかの不具合でデータがおかしくなった場合などに調査、復元がしやすいためだ。業務アプリで履歴データが無い、というのは今となっては考えられない。

履歴を実現するために、rails には vestal_versions や paper_trails など変更履歴を自動で記録してくれる便利な gem がある。これらを使うことで save のタイミングで逐一履歴を保存してくれるようになる。便利。しかし、これらのgemには一つ問題がある。これらは通常 before_save や after_save で実装されているため、save メソッドを使わずに sql を直接実行による更新をしてしまうと,
その変更の履歴が抜け落ちてしまう。これでは  mysql console からの操作が非常にやりにくい。

理想としては、言葉通り「全て」の変更履歴を残せる仕組みが良い。rails から保存しようが、mysql から直接 update かけようが、必ず履歴が残るようにしたい。そのような仕組みを実現するには、履歴を作成する機能がデータベースレベルで処理されることが必要となる。となるとデータベースの標準機能として存在しているのが最も好ましいのだが、残念ながらそのような機能を持つデータベースは見たことが無い。少なくとも mysql にはない。が、mysql の trigger を使って履歴を作成する機能を実装できる。それをざっくり説明する。

履歴のデータ構造としては acts_as_versioned などと同じ形式で、元テーブルと同じ構造+history_id(primary key) を持つテーブルを履歴対象テーブルごとに用意して、元テーブルに追加・変更・削除があるたびに履歴テーブルにデータを追記されていくようにする。

ではその履歴の追記をどうするかだが、mysql ではテーブルに対して insert, update, delete が行われたときに trigger を実行することができる。これを使って、insert, update, delete それぞれの処理時に履歴テーブルへ最新のデータがコピーされるような trigger を作成する。こうすることでDBレベルで履歴の記録が可能となる。(drop table, truncate table では履歴は作られない)

後は migration 時にこの trigger が自動で作成されるようにすればよい。create_table, change_table の実行時に trigger を作成するようにパッチを当てるだけでよい。

だた、この方法にも問題点はいくつかある。まず mysql の制限で、ひとつのテーブルに、ひとつの種別(insert, update, delete)に対して一つしか trigger を作ることができない。したがってこの履歴の仕組みを使った時点でtriggerはほかの用途には実質使えなくなる。

次に、常に履歴作成が走ってしまうというのも問題ではある。履歴が走る時と走らない時が全くコントロールできなくなる。1万行の update を実行すると同時に 1万行の履歴 insert が走る。ただこれについては全て mysql の中で自動で実行され、ruby のコードを介さないため、それほど大きな問題になっているという認識ではない。

また、これはなぜなのかよくわからないが、mysql の create trigger は異常に時間がかかる。数秒単位でかかるため、rake db:migrate でテーブルを最初から作るというときには数分単位で待たされてしまう。

最後に、これはもっとも深刻だが、当然ながらすべての履歴を保持するということはデータ量が莫大になるということになる。データ量x平均更新回数が履歴のサイズになるため、更新の頻繁なテーブルは履歴が爆発的に膨らんでしまう。そのようなものについては定期的にアーカイブが必要になり、そうした場合アプリの中で履歴を参照してロジックを実装してしまうとアーカイブもできず身動きが取れなくなる。

そういうデメリットはあるものの、履歴があるのと無いのとではトラブル時の安心感がまったく違う。間違ってデータを変更削除してしまった場合でも簡単に復旧できるので重宝する。もはや履歴が無いというのは考えられない。

2013年2月27日水曜日

Ruby 2.0 の refinement で DCI?

ruby 2.0 では refinement という機能が追加されています。これってDCIの実装に使えるのかも?というのが今回のテーマです。仕様が難しいので、実際動かしてみないと判断はできませんが。

注意:refinement は2013/02/27 時点でexperimentalですので、この記事が三日後ゴミになっている可能性はありますので注意してくださいね。

refinementとは?

まず簡単に refinement について説明します。refinement とは、モジュールの適用範囲を特定のファイル内のみに制限するための仕組みです。おっともうこの時点でなんか使えそうな気がしてきました。

http://jp.rubyist.net/magazine/?0041-200Special-refinement

からのサンプルコードを拝借すると、

# rationalize.rb
module Rationalize
  refine Fixnum do
    def /(other)
      quo(other)
    end
  end
end

という感じで定義します。通常の module と違うのは refine というブロックの存在です。ここで指定されたクラスにそのブロック内のメソッドをまとめて追加するぞ、という意味になります。もちろん複数のクラスを指定して、一気に refine することができます。

で、これをどう使うかというと、

using Rationalize
p 1 / 2 #=> (1/2)

使う側はこのような感じ。ファイルでこのように using で refinement を指定すると、そのファイルの中でのみ、refine 対象のクラスにメソッドが追加される、という動きになります。

DCI的に解釈

これをDCIに使えるのか?DCIではコンテキストを定め、そのコンテキスト以下においてはモデルに対して自動でroleが付与され、そのコンテキスト下でのみ実装の追加や差し替えが行われます。そしてそのコンテキストを抜け出たらそのroleは自動的に消滅します。

すると、そのコンテキスト下で各modelに付与すべきroleを refinement として定義しておき、そのコンテキストを表すファイルの先頭で using refinement する。すると refinement が role として各modelに付与される。その refinement はそのファイル内でのみ有効なので、コンテキストを抜けると自動的に role が消えます。

仕組みとしては良さそうです。また、特定コンテキスト下の role をひとまとめに書いておいて、それを一括で漏れ無くガツンと追加できる、というのもワタシ好みな感じです。今までのナイーブな rails での問題として、特定のコンテキスト下でのみ必要なメソッドが各モデルに分散してよくわからなくなる、という問題がありました。それがコンテキストという単位で一箇所にまとめることができるのは良いと思います(もちろん、モデルに置くべきか、コンテキスト単位でまとめるべきか、というのはケースバイケースだと思いますが)。また、このコンテキストで、いったいどのmoduleがextendされているのか?というのも一目瞭然なのが良いです。最もビジネスロジックレイヤーに近い、サービスなどの実装ではこの refinement が活躍しそうな予感で一杯です。

以上

2013年2月17日日曜日

考えるという事は問いを立てるという事

「何がわからないか」がわからないをそのままにしておかない技術モドキ

これと似た内容(劣化版)を昔社内の勉強会で発表したことがあるので、自分の言葉で書いてみたいと思った。

その時の発表のタイトルは「考えるとは何か」というものだった。考えるとはどういう行為なのか?入力は何で出力は何か?何ができたら「考える」という行為が達成できたことになるのだろうか?そういう疑問に答えを出したくて「考えた」結果の発表だった。

自分が過去そうだったのだが、数学のような抽象的思考(図形的思考)が得意な人の中には、なまじっか複雑なことを頭の中で図形的に処理して答えを出せるがために、考えるという行為が体系化されていない人がいる。そういう人は問題を解く際の戦略があまり洗練されておらず、難しい問題を解くときに頭の中にとりあえずその問題だけを突っ込んで、後はうーんと色々ランダムに考えてみて、時間をかけていつか答えが出るのを待つ。動物が餌を探す時に闇雲に歩きまわるような、そういう解き方をしてしまう人がいる。ちょっと極端な書き方をしたが自分が実際そうだったし、同じような人を何人か知っている。こういう、戦略のない、図形的思考にばかり頼る考え方のことを自戒を込めて「動物的思考」と呼ぶ。こういう人にはまず「言語的思考」を覚えてもらわないといけない。

動物的思考をする人は、色々な面で欠点がある。物事を図形的な思考にのみ頼ってなんとなーく処理しているので、細かいところで色々抜け落ちが出てくる。忘れっぽい。ノートも取らない。スケジュール管理もできない。机の上が散らかっている。おっと俺の悪口はそれまでだ!最大の特徴は、日常的に物事を言葉で表現する習慣がないので、とっさの質問に答えられなかったり、うまく言葉が出て来ないことがとても多い。難しい質問をすると黙り込むし、いきなり映画の感想とか聞かれても「よかったです」みたいな小学生のような答えしかでてこなくて恥ずかしい思いをする。夜になってあーあの時こう言っておけば良かったななんて後の祭りがチャメシゴト。このブログが要所要所で具体性に書けるのもそういうことだ。

私はこの自分自信の弱点を30年近い人生を経てやっと意識することができた。そしてここから脱却するために、自分の思考をもっと言葉をよく使うように変えていこうと考えた。そのための最初のステップが考えるとは何か?という問に答えを出すことだった。

では考えるとは何か?

私の定義では、考えるとは
  1. 問を曖昧さのない言葉で表現する。
  2. その問に答えを出す
の2ステップからなる。こちらのブログとほぼ同じ事を言っていると思う。そして、重要なのは1の方であり、動物的思考をする人がほとんどしていないのも1だ。動物的思考をする人はとても大雑把な問題をそのままの規模でいきなり解こうとしてしまう。するとその問題の規模が手に負えない場合、なかなか前に進まなくなる。こういう人が最初にやらなければいけないことは、問題の曖昧さをなくすことと、問題を処理可能な小さな問題になるまで分割していくことで問を洗練させるということだ。

問を洗練させるためには、まず問いがないといけない。自分が今いったいどんな問題を解こうとしているのか、自分のやりたいこと、ゴールは一体何なのか、頭の中にぼんやりとある問いを紙に書き出す。大雑把で良い。次に適当に書いた問いには5W1Hが抜けていること多いのでそこを明確にする。さらに、使われている一つ一つの単語の意味を全て拾い上げて明確に定義する。「かっこいい写真を撮りたい」というゴールだったとすると、それがいったい誰にとっての「かっこいい」なのか。かっこいいとは一体どういうことなのか、あるいはどういう物ではないのかを明らかにするということだ。そしてその実現のために必要なこと、障害となっていることは何か?ということを箇条書きにしていく。こういう整理を繰り返すと大きな問題がだんだんと小さな問題に分割されていく。当初は途方もなく大きく、どこから手をつけて良いかわからなかった問題が、ずっと規模が小さく、ゴールも明確で、なんとか手をつけれそうな問題になっていたりする。問題の最も難しい部分がどこなのかが明確になっていたり、一つ一つの小さな問題のできるできないが判断可能になっていたりする。これが問いを洗練させることの効果だ。

重要なことは、この問いを洗練させるという行為は、ある程度習熟すればかなり機械的な処理として実行できるということだ。もともと解こうとしていた途方もない問題に比べると極めて難易度の低い手続きのはずだ。簡単な整理をするだけで問題がずっと簡単になるのだから、やらない手はない。

このように、考えるという行為をはじめるときに紙の上で問題を整理していく作業をするようになってから、自分自信では物事を考えることの質も速度も向上したと感じている。こういう風に物事を整理して考える方法をもっと若い時に身に着けていれば人生変わっただろうとも思わなくはない。なので全国の動物系男子、動物系女子にはぜひとも自分の頭の中を紙にダンプして整理するという方法を身に着けてもらいたいなぁと思ってネットの片隅にこんな記事を書いた。

さらに補足として言っておきたいことが少しある。この「考える」の定義に従うならば、「一緒に考える」というのは考えている問いを誤解ない形で共有するということである、と再定義できる。また、森先生のブログにあった「考えたんですがまとまらなくて」という場合には、どこまで問いをブレイクダウンしたか、分割した中で一番むずかしい問いはどこか、その途中結果を共有するということになるだろう。「思い悩む」という状態は問いを明確にせずに「考え」ようとしてしまった結果ランダムな思考の迷路に入り込んでいる場合がある。他にもこの定義にしたがって捉え直すことのできるものがあるかもしれない。こういうことができるので言葉を自分なりに再定義するのは中々楽しい。

最後に、動物的思考をする人たちの名誉のために補足しておくと、この種の人は、その人がこれまで培ってきた多数のパターン認識という強い武器を持っている(というか、パターン認識能力に優れているから抽象的思考に強いのだと思う)。だから、パターン認識にすっぽりハマる分野では人並み以上の性能を出すことができているはずだ。ただ、それ以外の分野では抜けていることが多いという話。

2013年2月4日月曜日

view table を使って 1+N 問題回避

今回はtipsの紹介的な話。

検索結果で表示する内容が簡単な association だけでは取ってこれないような場合の解決方法について考える。

例えば、ユーザの検索結果にユーザの投稿数を表示する、という場合を考える。rails にはcounter cache columnという仕組みがあるためこの例に限っては別の解決法があるが、今回はそれを使わないことにする。

通常これを実装するためのコードは
@user = User.all

- @users.each do |u|
  %tr
     = u.posts.count

のようになるだろう。ここで明らかな問題は 1-N 問題が発生すること。posts.count を通るたびに

select count(*) from posts where user_id = 1

のような sql が実行される。u.posts.any?{|p| p.comments.present? } みたいなのだとさらに増える。

一つの解決法は当然ながら includes を使うことだ。
@user = User.includes( :posts => :comments )
としておけば 1-N 問題が解決される。しかしこれは数を数えるという目的のために comment オブジェクトを大量に作るという点で良くない。rails では残念なことに ActiveRecord の生成は大きなパフォーマンスの低下を招く。

別の方法としては、別途 sql で count した結果を inject してやるというものがある。
def inject_count_comments( posts )
  sql = "select post_id, count(*) from comments where post_id in (?) "
  restult = Comment.connection.execute [sql, posts.map(&:id)]
  posts_by_id = posts.index_by(&:id)
  result.each do |row|
     post = posts_by_id[""]
  end
end

これを毎回書くのは面倒なので、与えられた sql を実行して、model にセットするという汎用のメソッドは作ればすっきりする。model にセットするときの属性名は sql のカラム名をそのまま取るようにすればよい。

呼び出し側としては
posts = Post.some_condition.all
Post.inject_count_commitments( posts)
という二段構成になる点が残念。

さらに別な解決方法として、mysql の view を作るという方法もある。やや大げさすぎる感じもするので、これに値するほど複雑な sql のみ使っている。

最初の例の場合でいうと、以下のような view を作る。

create view posts_counts as
   select user_id, count(*) as count from posts group by user_id;

そして、この view を user の子モデルとして定義する。

class PostsCount < ActiveRecord::Base
end

class User < ActiveRecord::Base
  has_one :posts_count
end

@users = User.includes(:posts_count).all

ただこのやりかたは、Post がコメントをいくつ持っているか?という、明らかに Post の属性の一つであるものが独立したモデルとなってしまっているのがあまりよろしくなく、多様すると app/models や mysql の show tables が混雑してくるので限られた場合のみ使うほうが良いとは思う。コード上はすっきりするが。




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 と大差なくなる。