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


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 のパクリ丸出しだなー。


2012年9月19日水曜日

DCI (data context interaction)


DCI アーキテクチャという設計についての考え方がある。数年前から scala 界隈で盛り上がっていた記憶があるが、最近は ruby/rails 界隈でも盛り上がっている模様。

先日の札幌 ruby 会議で角谷氏が発表を行っている。
rubykaigi  http://sapporo.rubykaigi.org/2012/ja/schedule/details/79.html
スライド    http://kakutani.com/20120916.html#p01

そこからたどって以下のような資料があるのも発見した。
objects on rails (書籍の無料公開)
http://objectsonrails.com/

Clean Ruby
http://clean-ruby.com/
書籍サイト。ベータ版書籍が購入可能。$42なので電子書籍にしてはかなり高い。

DCIの講演 togetter
http://togetter.com/li/3335
色々disられていて面白い


私の意見も混ぜてざっと紹介してみます。

まず大元の問題意識としては、rails のアーキテクチャ(MVC)のレイヤ分割だけでうまくいくのはとても小規模なアプリのみということ。rails のレールに沿って素直にコードを書いていくといつのまにか super fat model. super fat helper が出来上がる。一つのクラスに大量のメソッドがある状態。これはC言語のように function が flat になっている状態と差がなくなるということだから非常に良くない。

従って、コードを責務などによって適切にレイヤー分割、クラス分割しないといけない。そしてそのレイヤー分割の判断根拠なるのはまずは「ビジネスロジック」や「ユーザエクスペリエンス」になる。
これらの「ドメイン」の境界に沿うようにプログラムの境界が設定されるのが自然なプログラム。
ビジネスロジックを理解せずにクラスやレイヤーの分割をするというのは大局では良い結果にならない。

   そういう意味で、例えばデザインパターンのような技術者だけの道具が設計の初期に出てくるのはおかしい。技術者はえてしてそういう技術話が好きなものだが、ラムダとかデザインパターンとかmix-inとか、ビジネスロジックの出てこない道具は「syntax上」の道具でしかなく、これはつまり局所最適化にしかなりえない。それを全体レベルの最適化に使ってもうまくいくはずがない。こういう道具は局所戦でしか使ってはいけない。

 まずはビジネスロジックやユーザエクスペリエンスに基づいて大枠の設計がなされ、局所的な最適化に各種ツールが活用される。その優先度が逆転してはいけない。(xxパターンに合致させるためにビジネスロジック上歪なクラス構成になってしまうなど。)ビジネスロジックに沿った境界の中を局所的なツールを使ってさらに分割するということは許されるが、プログラマの玩具によってビジネスロジックの境界が捻じ曲げられることがあってはいけない。

そしてビジネスロジックやユーザエクスぺリエンスに基づいた設計を行うために何を学ぶべきか?
というのがこのDCIや以前紹介した(かな?)DDD(Domein Driven Design)だ、と考えている。

大事なことはコードが「適切に」分割されることであって、分割されたコードをいかにruby 的に楽にmix-inするか?みたいな技術話(こういうのが局所最適化)は、コードを気持ち良く書くという短期的な幸せにとって大事ではあるが、長期的な幸せ(メンテナンス)にとっては影響度が相対的に低い。

色々大口たたいたけど自分も勉強中。。。

(追記)
そもそものDCIについて何もかいてなかったな。。。まぁ最近は紹介がたくさん転がっているのでいまさらですが、簡単に説明してみます。

DCI は上に書いた super fat model を解決するための方法、として紹介されていることが多いと思います。rails で thin controller を突き詰めると model がfatになる。大きなアプリになればなるほどビジネスロジック上の中核のモデルに大量のメソッドが生えてしまう。一番変更が激しいモデルが一番fatになっていく。そういう問題が起きます。

モデルが fat になると何がまずいか?コードの構造が読み取りづらくなり、他のメンバーが初めてコードを読んだ時に辛い思いをする。似たようなメソッドがたくさんできる。似たようなメソッドがたくさんできた時に、最初に作ったメソッドには active のようなシンプルで一般性の高い名前をつけてしまって困る。似たようなメソッドがたくさんあってよく分からなくなって、同じ名前のメソッドが定義されたり、同じ意味のメソッドが定義されたりする。などなど。

で、DCIではこれをどう解決しようとするのか。基本的な方針としては、モデルが使われるコンテキストごとに必要なメソッドを分類してモジュールに切り出して、必要なときに必要なモジュールを include しよう、ということになります。例えば入力ページを書くときだけに必要なメソッド、というのはよくあります。保存処理をするときだけ必要なメソッドなどもよくありますね。そういうものを models/user/input_role.rb や models/user/save_role.rb などのように別ファイルのモジュールに切り出して必要なときに include しましょう。とういことです。

えー、いちいち include するの面倒だよ、という人のために、DCI ではコンテキストという「場」を設けて、コンテキスト内に model が入ってきた時にそのコンテキスト用の role が自動で include されるような仕組みを作ろう、と言っています。ここについては上で少し局所最適化だ、とか吠えたように、私はそれほど重要ではないと思います。逆にこの仕組みがDCIだ、仕組みをどうやって作ろうか、みたいなところに話のフォーカスが写ってしまうとDCIの本質を見失った議論になると思います。

さて、ではmodelとroleを分けるとして、何を role に追い出すべきなのか?何が model に残っても良い、modelにとって本質的なものなのか?本来はここが議論されるべきポイントのはずです。この方針が決まる前に module が自動で include される仕組みが作られるというのは順序が逆だと思います。まぁ、ここはこれからじっくり時間をかけて合意が取られていくので。今の時点で明らかなものとしては

role に追い出すべきもの

  • 特定の場面で一回のみ使うようなメソッド。例えば退会処理を実行するメソッドなど。
  • view でのpresentationに関わるメソッド。
  • 一時的に association の動作を変えたい場合などに上書きする
model に残すべきもの
  • よく使うビジネスロジックを表現した scope。例えば、現在有効な会員データはregistered_on is not null and left_on is null だ、など。
  • model の状態を判定するようなメソッド。上のscopeをメソッドで表現したようなもの。active? とか left? など。
  • association
など。validation をどちらに置くべきかが議論の余地があると思う。save するときには必ず validation が走って正しいデータが保たれなければいけない、という場合は model に置くのが良いと思うし、業務アプリなどで、どうしても外部からの取り込みデータなどvalidationに通らないようなデータを格納しておかないといけない場合、常に validation が走ると batch 処理などがうまく走らなくなる。save :validate => false を使えば良いのだが、そういう validation を必要としない save がたくさんある場合は難しい。


最初の方で吠えていた局所最適化とかの話は↓でも似たようなことを書いてます。

コードの重複を排除するのもほどほどに!



2012年9月16日日曜日

ActiveRecord の changed? のバグ


ActiveRecord にはちょっとひどいバグ?がある。

ActiveRecord は save するときに実際に変更のあったカラムのみ
sql の update 文に書き出すようになっている。blob を持つようなモデルやカラム数がやたらと多いモデルの場合には sql が短くて済むので効果がある。

例えば

 u = User.find(1)
 u.name = "山田"
  u.name_changed? #=> true
 u.save
とすると、高速化のために name というカラムだけを更新するような sql が出力される。


同様に、
 u = User.find(1)
 u.save
としても何もカラムが変更されていないため、この場合そもそも sql が発行されない


これは良い仕組みだが、カラムが変更されたかどうかの判定に問題がある。
具体的には、代入が起き、かつ異なる値がセットされたときに変更があったとみなされるため、
gsub! を使った場合変更されたとみなされない。
 u = User.find(1)
 u.name.gsub!("山", "川")
 u.name_changed? #=> false
 u.save #=> なにもおきない

通常フォームからわたってくるデータを突っ込んで保存、という流れを想定しているので
あまり問題にはならないのだと思うが、バッチなどでこのようなコードを書いてしまう可能性はある。


2012年9月15日土曜日

コードの重複を排除するのもほどほどに!

コードの重複を排除する、ということは良いコードを書くための指針として昔から言われている。全く同じ構造のコードを1000行にわたって繰り返すことがばかげていることは誰にだってわかる。ruby はそのようなコードの重複を排除することに優れた言語だと思う。メタプログラミングを工夫することでかなり重複を排除することができる。

例えば

def opened?
  self.state == "opened"
end

def closed?
  self.state == "closed"
end

def waiting?
  self.state == "waiting"
end

のようなコードもメタプログラミングを使えば

%w(opened closed waiting).each do |name|
  define_method "#{name}?" do
    self.state.to_s == name
  end
end

のように書くことができる。

これはプログラマとしてはなかなか楽しい作業となる。コードの重複した部分を見つけて、うまいことメタプログラミングを使うことで、ちょっとしたパズル感覚で、しかもコードがぐっと短くなる。とても楽しい。

だから僕らはついついやりすぎてしまう。これが問題だ。コードの重複の排除というのは局所最適化でしかない。局所最適化を繰り返すことで全体最適化が達成されるというのは世界のほぼ全ての場所で成立しない。局所最適化のやりすぎは全体最適化を損なうというのは一般的な法則と言ってよい。

短いコードは良いコードだ。だがそれをどこまでも追及するとわけのわからんコードができる。短いコードが本当にいいコードなら、zip 圧縮してそれを読めばよい。

コードの重複を排除する、というルールをやりすぎると逆に読みづらいコードが出来上がる。重複排除がコードの品質の悪化につながるのはどういう場合か?重複の排除というのは通常同じ構造の部分をメソッドなりブロックなどで切り出すことで達成される。その切り出したコードがビジネスロジック上、あるいはアルゴリズム上などで明確で簡潔な名前をつけることができない場合、コードの品質は下がる。

機械的に重複部分だけをぴったり切り出した場合、そのようなことがよくおきる。そのようなメソッドにはよくわからないメソッド名がつけられてしまい、後からメソッドを読んだ時にそのメソッドが担っている役割が読み取りづらく、結果記憶もしづらい。メソッド名を読んでも何をしてるものなのかわからないから、結局コードの中身を一行一行読む、ということがおき、しかも繰り返す。無理やりわかりやすいメソッド名を付けても、それが内容とかい離しているようなら誤解を招くだけでより悪い。

だから簡潔なビジネスロジック上意味のある単位でコードを切り出すというのはとても重要なルールとなる。たとえそういうい切り出しによってすべての重複が排除できなかったとしても、それは気にしなくてよい。メソッドにはわかりやすい名前をつけよう、とよく言われているが、それは順序が逆なのだと思う。わかりやすい名前をつけることができるメソッドの切り出し方にしよう、というのが本来あるべき姿のはずだ。

このおかしさをわかってもらうためにしつこく説明を繰り返すが、コードから機械的に重複部分を切り出して、そこにドメインの単語をつける、というのはあってはならない。前者はシンタックスで後者はセマンティクスだ。表現すべき意味がまずあってそれをコードで表現しているはずなのに、そのコードを機械的に意味を考えずに切り貼りして、そのコードの断片がドメイン上偶然意味を持つか?という話。

具体例が出せるともっと伝わるんだろうな。もしコード書いてて具体例に出会ったらまた追記します。

2012年9月13日木曜日

before_save はやっぱり使いづらい

before_save は filter としての役割と、hook としての役割の両方が重なっている。それが便利でもあるが、バグを生む原因にもなる。

before_save はそのブロックが false を返すと save の実行自体をやめてしまうというフィルターとしての役割がある。そうとは知らずにsaveの前に実行される hook としてのみ before_save を扱ってしまうと、うっかり before_save が false を返してしまったときに保存されないという痛い目を見る。

まぁそれがメインのモデルならまだ気づきやすいのだが、子供や孫モデルだったりすると発見が遅れてしまう。

2012年9月11日火曜日

scope

AR の scope はとても良い。AREL によって表現力が高まってからは本当に使い勝手が良い。

scope は何が良いかというとビジネスロジック上重要な概念に名前を付けてパッケージ化できるところがよい。複雑な sql を書いた場合、join, where、group by, order by などに散らばった複数の sql の断片がセットになっていることが多い。

例えば以下の例を見る。


select u.id
  from users u
         left outer join user_activation ua
            on ua.user_id = u.id
         left outer join user_emils ue
            on ue.user_id = u.id
 where ua.activated_on is not null
    and ue.emails = ""
 order by u.id

これは、activateされたユーザの中から email をまだ登録していないものを抽出する、という意味になる。activate された、という条件が user_activation のjoin と 最初の where 句に散らばっている。email についても同様だ。

scope を使うと sql では文法の制限上バラバラに配置されていたものが意味のまとまりによって再配置することができる。

scope :activated, joins(:user_activations).where("ua.activated_on is not null")
scope :has_not_email, joins(:user_emails).where(:user_emails => { :email => "" })

クエリは以下のようにかける。

userss = User.activated.has_no_email.order(:id).select(:id)

非常に読みやすくなる。実際の開発ではこれよりもはるかに複雑な sql を書いているが、scope のおかげでかなり理解しやすい。sql ゴリゴリ書いての開発なんてぞっとする。

なお、order(:id) や select(:id) は scope に含めることはあまりしないし、別途 scope を定義することもあまりしない。

scope :order_by_id, order(:id)

のような、名前がそれほど意味を圧縮できない場合にはうまみがないし、そのような scope が乱立するとコードがごちゃごちゃしてくる。このあたりは単純に、同じものがくりかして出てくるかどうかで scope にするかどうか、適当にまとめたらよいと思う。

before_saveの弊害

before_save, after_save は中々便利な機能で、このような何かをフックに勝手に処理が走ってくれる、という仕組みは小規模なアプリでは大変楽にプログラムが書ける。save すれば必ず処理を走らせてくれるのだからうっかり忘れることが無い。

しかし、ある程度大きなプログラムになってくるとこれが厄介者となってくる。save したときに必ずhookを走らせるというのは、どんな時もhookが走るのだから整合性を保つうえでは楽な仕組みではあるが、例えばパフォーマンスに目を向けたときには厄介なものとなる。バッチで10万件のデータの save を行う、みたいな場合にヘビーなhookが自動で走ってしまうと問題が起きてしまう。そもそもその save は例えば特定のフラグを立てるなど、簡単な更新処理のはずなのに、 validation に引っかかってしまって保存できずにバッチ処理の処理もれが発生する、などが起きるとまずい。

validateion だけなら save :validate => false したり、update_attribute を使えば回避できるが、validate => false は association を伝搬しないし、update_attribute は一つのカラムしか更新できない(おまけにもうobsolete)。他にも before_save をスキップしたいときというのはいくらでもある。本来は入力画面から保存したときだけは知ればよいはずのhook を簡単で確実、という理由で before_save にしたばっかりに、どこで save しても hook が走るようになってしまい困る、ということが何度か起きていた。アプリの規模が大きくなるとhookを1.動かしたい場所、2.動かしても構わない場所、3.動かしては困る場所のうち3がどうしても増えてくる。そうなると結局細かくhookを実行するかどうかの制御をしないといけなくなる。before_save はそれが実行されることが若干わかりづらいために、特に複数人開発などではだんだんと混乱してくる。

そのために before_save はあまり使わないようにしている。代わりに、先のポストに書いた、機能ごとのモジュール内でhookを定義するようにしている。こうするとたとえば入力画面で保存したときは必ずhookが走ることが保証される。モデルそのものに hook を定義することはしない。



model の module 化

巨大な rails アプリを作っていると、すべての業務ロジックをモデルに書くというrails標準の考え方はすぐに破たんする。最もコアのモデルだと数千行を簡単に超えてしまうためである。長すぎるメソッドが良くないのと同様に、長すぎるクラスもよいものではない。

そこで module やラッパーを使って、特定の場面でのみ必要な機能を付加する、ということを考える。通常の rails の考え方では、モデルにはそのモデルに関係したビジネスロジックを書く、ということになっているが、そのモデルのビジネスロジックのうち、特定の場面でのみ必要なロジックを「場面xモデル」の単位でコードを切り出してmodule化しておき、必要なときにそれを include するということをしている。

例えば入力画面でのみ必要なメソッド、というのはよくあるはず。確認画面に表示するためのメソッドだったり、入力フォームを構築するために必要なちょっとしたメソッドなど。このような、本来のモデルのビジネスロジックとは無関係な、特定の機能のためのロジックを models/user/input.rb などに追いやって、実行時に都度includeすることでコードをすっきりさせる。

このような考えを進めたものに DCI というものがある。モデルにすべてのロジックをおくことはせずにモデルxビジネスロジックごとにモジュールを用意しておいて、ビジネスロジックが実行されるときにモジュールが自動的に読み込まれるようにする、という仕組みだ。最近 rails 界隈でもはやっている。

切り出すモジュールとしてよくあるのは

  • 表示用のメソッド。これがたぶん一番多い。たとえば住所を表示する際に郵便番号から番地までひとつの文字列として出したい場合や、それぞれ別々に改行して出したい場合、市区町村以下しか必要ない場合、などいろいろある。そういう細かい違いだけのメソッドが大量に混ざるとコードがどんどんごちゃごちゃするし、どこで何が使われているのか把握できなくなる。exhibit オブジェクトとか presenter とか言う。
  • validation など編集時だけに必要なメソッド。
  • 各種ビジネスロジックごとのメソッド。バッチファイルでのみ必要なメソッドなどを突っ込む
モジュールを include するのをどうするかという問題がある。モデル単体であれば簡単だが関連上のモデル全部に対して extend していくのはなかなか面倒だ。

自分は traverse というオブジェクトツリーをたどるライブラリを自作しており、これを使っている。

user.acts_as! :list_presenter, [ :emails, :tels, :user_groups => :representative ]

などとして一覧表示用のメソッドを追加する。最後の引数は Arel の includes などに渡す hashと同じ形式のものを渡す。そうすると Arel と同様にアソシエーションを解釈して関連にも module を extend していってくれる。





ghostscript の dithering がひどい件

gs で pdf => 白黒tiff に変換すると、文字部分は問題ないが画像部分に dithering が発生してかなりひどいクオリティとなる。細かい文書だと読むのは不可能になってしまう。

gs -dBATCH  -sDEVICE=tiffg3  -dNOPAUSE  -sOutputFile=output.tiff -r200 -f input.pdf

これを回避するために gs のオプションを設定して何とかしようと試みたが、どれもあまりうまくいかない。というかどうやっても無理だった。なのでもうあきらめて imagemagick を使うことで回避する。ただ、imagemagick は imagemagick で PDF のフォントの扱いに難があって文字が異様に小さくなったりしてこれも調整が面倒になる。

ということで、以下のような2段階の方法を取ることにする。まず gs で PDF => カラー画像(jpg, png など)に変換し、その後 convert を使って tiff に変換する。


gs -dBATCH  -sDEVICE=jpeg  -dNOPAUSE  -sOutputFile=output$04d.jpg -r200 -f input.pdf
convert -page A4 -density 200 output*.jpg -compress Group4 -colorspace GRAY -colors 2 -normalize output.tiff


こうすることでまともな tiff が得られる。ただしこれも少し難点がある。JPG にしたときに若干モアレが発生してしまい、それが最終的な白黒画像に若干のノイズドットとして残ってしまう。

中間のフォーマットを png にすればこれは解決するのだが、png はtiffと同様にそのフォーマットに pixel 数だけでなく resolution(dpi) を持っており、同じpixel数でも dpi が異なれば印刷したときなど違う用紙サイズとなる。これは tiff にしたときにも引き継がれてしまい、最終的なoutputが通常の1/3サイズの紙になってしまう。これはまだ解決できていない。jpgには resolution という概念はなく pixel のみなのでこのような問題は起きない。

2012年9月10日月曜日

ruby の Iconv の文字変換エラー対策

Iconv は変換不可能な文字があった場合にIllegalSequenceエラーを返してくれる。勝手に文字を無視されて知らない間に変なデータが登録されてしまうよりかは良いが、無視をしたい場合はある。たとえばユーザのメアド一覧をCSV出力したい、というときに名前に中国の漢字が含まれているためにCSVが出力できない、というのは好ましくない。

このような目的のためのオプションが Iconv には用意されている。

 str = Iconv.conv("CP932//IGNORE", "UTF-8", str)

とすれば、変換不可能な文字は単に無視される。

使いどころを間違うと問題になりうるオプションではある。