Ruby on Rails: なぜActiveRecordが必要なのか?

 Railsの勉強がしばらくストップしてしまったので、今日はビデオを見てお勉強。Rails Envyの「ActiveRecord Tutorial」は長さも25分とちょうど良いし、「ActiveRecordとはなんぞや」を具体例を交えて簡潔に教えてくれるのでとても良い勉強になる。

 英語だが、冒頭の部分を乗り越えればあとはプログラミングの話なので、日本人にもそれほど難しくないはず。念のため、オープニングの部分のみ、超訳しておいた。

ActiveRecordのアイデアは、いったいどこから来たのか?

まずは"Active Record"の意味から (ActiveRecordではない点に注意)
 "Active Record"とは、デザイン・パターンの一つ。
 どうやってデータベースにアクセスするか?
  SQLにプログラムから直接アクセスする方法もあるが...ちょっと不便
 データベースのテーブルをオブジェクトにマップすればいい
 というのが"Active Record"の考え方
 このデザイン・パターンを、
  PHPに応用したのがCakePHP
  Rubyに応用したのがActiveRecord

つまり、言い換えれば「ActiveRecordはRubyをSQLに翻訳する仕組み」。

Rubyで u = User.find(1) が、
SQLでは、 SELECT FROM users WHERE (users.`id` = 1) となる

Ruby: u.first_name = "Joe"
SQL: (まだ何もしない)
Ruby: u.save
SQL: UPDATE users SET `first_name` = 'Joe' WHERE `id` = 1

Ruby: u = User.find_by_first_name("Joe")
SQL: SELECT * FROM users WHERE (users.`first_name` = 'Joe') LIMIT 1

 しかし、こんなハイクオリティなものが、ちょっとググッただけで簡単に見つかるとは本当に良い時代になったものだ。

【追記】ちなみに、同じ人たちが「Ruby vs. PHP」の論争を「Mac vs. PC」CMのパロディにしたビデオを作っていることを発見したので、ここに貼付けておく。


教えながら学ぶRuby:「Rangeの積を求める」をやってみた

 今日は必要もなく早起きしてしまったので、Rubyに感して書かれた過去のブログなどをネットサーフィング。すると、「Rangeの積を求める」に遭遇。この手のものに出会うと、かならず自分で解きたくなるのが私の性分。1年以上の激遅レス。

class Range
 def &(dest)
  return nil if self.max <= dest.min or dest.max <= self.min
  return ([self.min, dest.min].max..[self.max, dest.max].min)
 end
end

 Rubyでmin/maxをどう書けば良いのか分からず少し苦労してしまったが、Arrayを使うのだと一度理解してしまえば簡単。たかがmin/maxのためにオブジェクトを作るという点にはどうも抵抗があるが、これは慣れるしかないのだろう。


オブジェクトを次々に渡す「Ruby Filter」ってどうだろう

 Rubyに慣れようと、コマンドライン・ツールなどを作ってみることにしたのだが、すでにUnixに存在しているgrepなどを作っても仕方がない。そこで、指定したブログのURLからHTMLページをHTTP GETで取得し、それをパースしてATOMやRSSフィードのURLを見つけて、それをさらにHTTP GETで取得してタイトルだけ表示する、というツールを作ってみることにした。

 できるだけRubyらしい作り方をしようと思いついたのが「Ruby Filter」。Unixのフィルターのようにそれぞれは単一の機能を持ったプログラムをパイプでつなげて複雑なことをさせる。ただし、フィルターからフィルターに渡すものは単なるテキストではなく、オブジェクトのテキスト表現だ(次のフィルターはそのテキストをevalしてから入力として利用する)。

 上のブログのURLからRSSフィードを取り出すケースだと、

 parseURI | getHTTP | parseHTML | extract rss10 | parseURI | getHTTP | parseRSS | eachPrint title

という組み合わせで実現できる。

 このブログのURL "http://satoshi.blogs.com/" を食わせた場合の処理は以下の通りになる。

ステップ1. parseURI
 入力: "http://satoshi.blogs.com/"
 出力: { :port=>80, :path=>"/", :scheme=>"http:, :host=>"satoshi.blogs.com" }

ステップ2. getHTTP
 入力: { :port=>80, :path=>"/", :scheme=>"http:, :host=>"satoshi.blogs.com" }
 出力: "<!DOCTYPE html ...(中略)...</html>"

ステップ3. parseHTML
 入力: "<!DOCTYPE html ...(中略)...</html>"
 出力: {:rss10=>"http://satoshi.blogs.com/life/index.rdf", rss20=>"http://satoshi.blogs.com/life/rss.xml", :atom=>"http://satoshi.blogs.com/life/atom.xml", :title=>"Life is beautiful"}

ステップ4. extract rss10 (注:パラメータが必要)
 入力: {:rss10=>"http://satoshi.blogs.com/life/index.rdf", rss20=>"http://satoshi.blogs.com/life/rss.xml", :atom=>"http://satoshi.blogs.com/life/atom.xml", :title=>"Life is beautiful"}
 出力: "http://satoshi.blogs.com/life/index.rdf"

ステップ5. parseURI
 入力: "http://satoshi.blogs.com/life/index.rdf"
 出力: { :port=>80, :path=>"/life/index.rdf", :scheme=>"http:, :host=>"satoshi.blogs.com" }

ステップ6. getHTTP
 入力: { :port=>80, :path=>"/life/index.rdf", :scheme=>"http:, :host=>"satoshi.blogs.com" }
 出力: "<?xml version=...(中略)...</rdf:RDF>"

ステップ7. parseRSS
 入力: "<?xml version=...(中略)...</rdf:RDF>"
 出力: [{:url=>"http://satoshi...", :title=>"..."}, {:url=>"...", :title=>"..."}, ...]

ステップ8. printEach title (注:これだけは出力が人間向け)
 入力: [{:url=>"http://satoshi...", :title=>"..."}, {:url=>"...", :title=>"..."}, ...]
 出力: (人間向けの)タイトル一覧

 結構面白いと思うんだがどうだろう。

 参考までにソースコードを乗せておく(注:Ruby初心者なので、あまり良いコードではないかも知れない)。

parseURI

#!/usr/bin/ruby
require 'uri'
require 'filter'
Filter.evalStdin
uri = URI.parse($val)
ret = {:scheme=>uri.scheme, :host=>uri.host, :port=>uri.port, :path=>uri.path}
p ret

getHTTP

#!/usr/bin/ruby
require 'net/http'
require 'filter'
Filter.evalStdin
Net::HTTP.version_1_2 # omajinai
Net::HTTP.start($val[:host], $val[:port]) { |http|
ret = http.get($val[:path])
p ret.body
}

parseHTML

#!/usr/bin/ruby
require 'filter'
Filter.evalStdin
ret = { :title=>nil, :rss10=>nil, :rss20=>nil, :atom=>nil }
ret[:title]=$1 if /<title>(.*)<¥/title>/ =~ $val
ret[:rss20]=$1 if /<link.*title="RSS 2.0".*href="(.*)".*¥/>/ =~ $val
ret[:rss10]=$1 if /<link.*title="RSS".*href="(.*)".*¥/>/ =~ $val
ret[:rss10]=$1 if /<link.*title="RSS 1.0".*href="(.*)".*¥/>/ =~ $val
ret[:atom]=$1 if /<link.*title="Atom".*href="(.*)".*¥/>/ =~ $val
p ret

extract

#!/usr/bin/ruby
require 'filter'
key=ARGV.shift
Filter.evalStdin
p $val[key.to_sym]

parseRSS

#!/usr/bin/ruby
require 'rexml/document'
require 'filter'
Filter.evalStdin
ret=[]
doc = REXML::Document.new $val
doc.elements.each('//item') { |e|
url=e.attributes['about']
item = { :url=>url, :title=>nil }
e.each_element('title') { |ee| item[:title]=ee.text }
ret.push(item)
}
p ret

printEach

#!/usr/bin/ruby
require 'filter'
key=ARGV.shift.to_sym
Filter.evalStdin
$val.each { |e| printf "%s¥n", e[key] }

filter.rb

class Filter
def self.evalStdin
input = "$val="
until $stdin.eof?
input += gets
end
eval(input)
end
end


教えながら学ぶRuby: 言語を拡張したくなる衝動に関して

 少しMBAの勉強の方が一段落したので、今日はRubyの勉強。「Ruby本」にサンプルとして掲載されているチャットのプログラムを色々な技巧を使って「どこまで美しくできるか」を試みるのが今日の課題。そこで悩んでしまったのが、「やたらと言語を拡張したくなる衝動」を押さえるべきかどうか。Rubyの場合、すべてのものがオブジェクトで、かつ、すでに存在するクラスにメソッドを自由に追加できるので、FixNumだとかNilClassなど基本的なクラスの再定義をすることにより、あたかも言語を拡張しているような効果を生むことが可能なのだ。

 今日書いていて気に入らなかったのは下のコードの太字の部分。

while([email protected])
 r,w,e = select(@socks)
 next if r.nil?
 r.each { |sock|
  case sock
  ...
 }
}

 これは、selectから返されたrがnilだった場合に、それに続くr.eachでエラーにならないようにするための処理だが(注:実際にはタイムアウトのパラメターを与えていないのでnilが返るはずはないのだが、とりあえずそれは無視していただきたい)、この一行がどうにも読みにくくしている。

 そこで私がすかさず思ったのは、「NilClassにeachメソッドを加えれば良いじゃん」である。

class NilClass
 def each
 end
end

「nilのすべての要素に対して何かをする=何もしない」なので、論理的にも正しい。こうしておけば、上のプログラムは、

while([email protected])
 r,w,e = select(@socks)
 r.each { |sock|
  case sock
  ...
 }
}

とすることができ、ずっと読みやすくなる。

 Smalltalkもそうであったが、Rubyを書いていると、こんな風にシステムで提供されているクラスを再定義したいという衝動に駆られる。こんな衝動に、ちまたのRubyistたちはどう対処しているのだろう?

 上の例のように、システムクラスを拡張することにより、(その拡張を理解した)自分自身にとってのコードの可読性を上げることは可能だ。しかし、複数の人間が関わるプロジェクトでそれぞれのエンジニアが勝手にシステムクラスの再定義を初めてしまっては支離滅裂になってしまう。かといって、いちいち相談していては効率が悪い。結局のところは、Railsのように一人とか二人とかいったごく少数の人がフレームワーク構築の一環としてシステムクラスに手を入れ、他の人たちはその上に乗っかるようにプログラムを作る、というスタイルが現実的なのだろうか 。増井さんが指摘したように、確かにこれがRailsを使う大きなメリットの一つ、なのかも知れない。


RailsがRubyで作られた本当の理由

弾:最初の質問です。なぜRubyを選んだのですか?

DHH:極端なことを言うと,Rubyが一番美しく自分のコードが書けるからです。

小飼弾のアルファギークに逢いたい♥:#2 Ruby on Rails作者 David Heinemeier Hansson(前編) RubyでRailsを書いたわけ|gihyo.jpより引用】

 この記事を既に読んだ方も多いと思うが、この「Rubyが一番美しく自分のコードが書ける」というセリフは非常に重要である。「イテレータに片思い」というエントリーで書いた通り、Rubyには生みの親の「コードは読みやすくあるべき」という魂がしっかりと込められており、それが「コードの美しさ」に繋がっているのである。

 私がRubyを触り始めて一番強く感じたことは、Smalltalkとの類似点である。私自身、90年に数ヶ月間Smalltalkにどっぷりと使っていた時期があるが(マイクロソフトで「次世代OS」のプロトタイプを作っていたのである)、その時にもっとも感動したのは、Smalltalkが自己増殖型のプログラムを書くことを許してくれることであった。自己増殖型のプログラムとは私の造語であるが、オブジェクト指向型の言語でプログラムがダイナミックにクラスを生成して自分自身を拡張していくスタイルのプログラムである。

 「次世代OSはオブジェクト指向で」というかけ声で作り始めたプロトタイプであったが、最初に悩んだのが、WordだとかExcelなどのアプリケーションが作るアプリケーション独自のファイルタイプをどう扱うか。WindowsのRegistryのように独自のデータベースを作るのも一つの方法であったが、そうするとシステムレベルでのクラスと、アプリケーションレベルでのクラスという二つの異なるクラス空間ができてしまう。そこがいやだった私は、新しいアプリケーションが登録されるごとに、ファイルタイプに相当するシステムレベルのクラス(例えば、WordにはWordFile、ExcelにはExcelFile)をダイナミックに生成し、そこにコンテキスト・メニュー(ファイルを右クリックしたときに表示されるポップアップ・メニュー)に表示すべきコマンドをmethodとして登録する、という技を使うことにより、クラスを一元化したのである(残念なことにそれを商品化したWindow95はC++で作ったために、そんな技は使えなかったが、私としては未だにSmalltalkで作ったプロトタイプの方がアーキテクチャ的には美しかったと思っている)。

 で、Rubyに話を戻すと、私がRubyに感じたSmalltalkとの類似点とは、まさにこの「自己拡張型のプログラミング」を可能にする点である。RailsのActiveRecord上でのプログラムがあれほどナチュラルに美しく書けるのは、RailsがこのRubyの柔軟性を最大限に活用し、データベースのテーブルのスキーマを読み込んでそれに対応するクラスをダイナミックに生成しているからに他ならない。

 つまり、RubyなりSmalltalk上でのプログラムの「美しさ」が最も強く発揮されるのは、プログラマーにとって「こう書ければ美しくなる」と思えるようなクラスのハイアラキーをフレームワーク自身が自動生成してくれる時である。Railsがこれほど高く評価されるのは、まさにそんなフレームワークだからなのである。逆の言い方をすれば、こんなフレームワークを作るにはC++やJavaやPerlやPHPでは無理で、Railsを作るにはRubyかSmalltalkを使うしかなかったのである。


教えながら学ぶRuby: それでも僕は君をイテレータと呼ぶ

 前回の「イテレータに片思い」、たくさんのフィードバックをいただき、とても良い勉強になっている。一人でこつこつと勉強するよりも、励みになるしずっと楽しい。

 ちなみに、1問目の問題だが、

def hello(to, *mesg)  print "Hello, ", to, ".\n"

 print "-- message -- \n"

 for m in mesg

  print m, "\n"

 end

end

これに関しては、予想した通り、ほとんどの人が私と同じく、

def hello(to, *mesg)  print "Hello, ", to, ".\n"

 print "-- message -- \n"

 mesg.each { |m| print m, "\n" }

end

という答えにたどりついた({..} のかわりに do..end でも良い)。圧倒的に読みやすいし、とにかく美しい。「プログラムはできるだけ読みやすくあるべき」というポリシーの私としては、これだけで「Rubyに一目惚れ」である。生みの親のまつもとゆきひろ氏の「読みやすいコード」への思いが伝わってくる。

 ところが、2問目の問題、

def fact(n)  return 1 if n==0

 f=1

 while n>0

  f*=n

  n -= 1

 end

 return f

end

に関しては、私の用意していた答えは、

def fact(n)

 f=1

 (1..n).each { |i| f*=i }

 return f

end

なのだが、他にも色々な書き方があるようで、

def fact(n)

 f=1

 n.downto 1 do

  |i| f*=i

 end

 return f

end

などのバラエティも寄せられ、「同じことが色々な方法ができるというのはあまり美しくないな」ともう二回目のデートから相手のあらが見えてしまうしまつ。そんなときに、

def fact(n)

 (1..n).inject(1) {|r, i| r*i}

end

という私がまだ学んでいない inject なるもので f=1 を排除したものが寄せられ、「うむむ」と思わせるあたりが男心をくすぐるというか何と言うか。

 ちなみに、最近はこのイテレータのことはイテレータとは呼ばずにブロックなどと呼ぶらしいが、私としては生みの親の気持ちを尊重してイテレータと呼び続けたい。

1回しか呼ばれなかろうと  時には一度も呼ばれなかろうと   それでも君はイテレータ

教えながら学ぶRuby:イテレータに片思い

 ということでやっとRubyの勉強を始めた私だが、「何かを学ぶには人に教えるのが一番」というポリシーの私としては、早速Rubyに関するエントリーを書かずにはいられない。

 Rubyという言語に関しては色々な意見があるとは思うが、私が(いまのところ)一番気に入っているのはイテレータという考え方。これは美しい。できることならば、forとかwhileのない言語にしてくれればもっと美しかったのにと思うぐらいだ。ここからしばらくは、forとwhileを一切使わずにどこまで出来るかを試してみようと思う。

 そう思いながら読み始めた「オブジェクト指向スクリプト言語Ruby(以下「Ruby本」)」。Rubyの生みの親であるまつもとゆきひろ氏自身の手になるものだ。彼なりのイテレータへの思い入れが伝わってくるに違いない...

 ところが、である。

 この本の最初の方に出てくるサンプルがなぜかイテレータを使わずにwhileとforで書かれているのだ。うーん、イテレータに対する私の思いは片思いなのか?と思いながらも、それぞれのサンプルをイテレータを使って書き直して、「こっちの方が美しい」と自己満足に浸っている私である。

 ということで、ここで問題。以下の二つのプログラムをfor/whileを使わずにイテレータで書き直してみていただきたい。私はどちらの例も、イテレータで書いた方がずっと美しいと思うのだが、いかがだろう。

問題1(「Ruby本」ページ42より引用)

def hello(to, *mesg)
 print "Hello, ", to, ".\n"
 print "-- message -- \n"
 for m in mesg
  print m, "\n"
 end
end

問題2(「Ruby本」ページ66より引用)

def fact(n)
 return 1 if n==0
 f=1
 while n>0
  f*=n
  n -= 1
 end
 return f
end