Personal tools
You are here: Home ブログ 井上 RubyはJavaより難しい(と思う) - Railsの acts_as の話 -
« December 2010 »
Su Mo Tu We Th Fr Sa
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
Recent entries
Apache2.4のリリース予定は来年(2011年)初め(あくまで予定) inoue 2010-12-23
Herokuの発音 inoue 2010-12-20
雑誌記事「ソフトウェア・テストPRESS Vol.9」の原稿公開 inoue 2010-12-18
IPA未踏のニュース inoue 2010-12-15
労基法とチキンゲーム inoue 2010-12-06
フロントエンドエンジニア inoue 2010-12-03
ASCII.technologies誌にMapReduceの記事を書きました inoue 2010-11-25
技術評論社パーフェクトシリーズ絶賛発売中 inoue 2010-11-24
雑誌連載「Emacsのトラノマキ」の原稿(part8)公開 inoue 2010-11-22
RESTの当惑 inoue 2010-11-22
「プログラマのためのUXチートシート」を作りました inoue 2010-11-19
「ビューティフルコード」を読みました inoue 2010-11-16
Categories
カテゴリなし
 
Document Actions

RubyはJavaより難しい(と思う) - Railsの acts_as の話 -

Rubyはやはり難しいです。

まず記号が多い点があげられます(http://www.ruby-lang.org/ja/man/html/Ruby_A4C7BBC8A4EFA4ECA4EBB5ADB9E6A4CEB0D5CCA3.html#a.3a)。一般的には、記号の多さは可読性を落とす気がします。 そして、(Railsが顕著すぎるのかもしれませんが)メタプログラミングの多用があります。メタプログラミングはクールですが、一般的には分かりやすいコードとは言えません。 そして、個人的見解ですが、そもそも未だに動的型言語が正しいとは思えていません。変数の型を明示しないことが便利に働く場面があることは認めます。Cで言えば、プリプロセッサマクロやvoidポインタで多態的なコードを書くような場面です。しかし、多く見積もっても、型を明示しないことが便利なケースは全体の20%程度だと思います。20%は結構多く見積もっているつもりです。残り80%は、変数に型を明示した方が可読性が上がると思います。書く側にも読む側にも安心感が得られるからです。そしてコンパイルさえ通せば、想定している型と違うオブジェクトだったという、あまりに基本的なバグから(ほぼ)逃れられます。

ぼくがプログラミングで最も重視するのは、読み手のコストです。読み手のコストを減らすためには、書き手は相当のコストを背負っても良いと思っています。

と、Ruby大嫌い人間みたいですが、Rubyの話です。

Ruby on Railsのpluginのコードの話です。acts_as_listを例にコードを見てみます。コード例はv2.0.2ベースです。

init.rbは以下のようになっています。

$:.unshift "#{File.dirname(__FILE__)}/lib"
require 'active_record/acts/list'
ActiveRecord::Base.class_eval { include ActiveRecord::Acts::List }

これを呼び出す場所は調べていませんが、pluginsディレクトリ下にあるinit.rbをロードする処理がどこかにあるはずです。

最初の行はロードパス変数 $: (型はArray) に相対パスを挿入しています。次の行で相対パスのlist.rbファイルをロードします。list.rbファイルは後で見ます。 3行目で、既存クラスの ActiveRecord::Base にモジュールをincludeしています。class_evalに渡すブロック内の self はActiveRecord::Baseのクラスオブジェクトを参照しています。 ここは、次のように書いてもいいはずです。

class ActiveRecord::Base
  include ActiveRecord::Acts::List
end

list.rbの骨子だけ抜き出すと次のようなコードになっています(コメントはごっそり削除)。

module ActiveRecord
  module Acts #:nodoc:
    module List #:nodoc:
      def self.included(base)
        base.extend(ClassMethods)
      end

      module ClassMethods
        def acts_as_list(options = {})
          configuration = { :column => "position", :scope => "1 = 1" }
          configuration.update(options) if options.is_a?(Hash)

          configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/

          if configuration[:scope].is_a?(Symbol)
            scope_condition_method = %(
              def scope_condition
                if #{configuration[:scope].to_s}.nil?
                  "#{configuration[:scope].to_s} IS NULL"
                else
                  "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
                end
              end
            )
          else
            scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
          end

          class_eval <<-EOV
            include ActiveRecord::Acts::List::InstanceMethods

            def acts_as_list_class
              ::#{self.name}
            end

            def position_column
              '#{configuration[:column]}'
            end

            #{scope_condition_method}

            before_destroy :remove_from_list
            before_create  :add_to_list_bottom
          EOV
        end
      end

      module InstanceMethods
        def insert_at(position = 1)
          insert_at_position(position)
        end
        多くのメソッド...(略)
      end 
    end
  end
end

このコードをぱっと見て、どこがどんなタイミングで呼ばれるかすぐに分かればすごいです。少なくともぼくは分かりません。

Rubyの考え方として、上のinit.rbからrequireされたタイミングで、list.rbのコードがすべて評価される(実行される)と考えてください。例えばもし、module Listの行の下に print "foobar" と書いてあれば、requireされた直後に foobar が出力されます。実際のlist.rbの中にはdefしかないので、defの中身はメソッド定義として評価され、中身が実行されるのは各メソッドが呼ばれた時になります。

最初のポイントは、self.included がinit.rbの3行目のincludeのタイミングで呼ばれることです。includedメソッドの動作は次のようにirbで確認できます。

irb> module M
       def self.included(m)
         p "M included"
       end
     end

irb> class C 
       include M
     end
"M included"

includedメソッドの中身は base.extend(ClassMethods) です。この base は ActiveRecord::Base のクラスオブジェクトを参照しています。extendはオブジェクトにモジュールを加えるメソッドです。この場合、クラスオブジェクトにモジュールをextendしているので、意味的には次と同じ処理になります。

class ActiveRecord::Base
  class << self
    include ClassMethods
  end
end

ClassMethodsの中にはacts_as_listがあります。これは次のように呼ばれることを想定しています(Booksは開発者が作るクラス)。

class Books < ActiveRecord::Base
  acts_as_list
end

上のように呼ばれると、acts_as_list内のclass_evalが実行されます。この時、class_eval内の self は Books を参照しています。意味的には次のようなincludeと同じになります。

class Books
  include ActiveRecord::Acts::List::InstanceMethods
end

これでめでたくBooksクラスのオブジェクトは、InstanceMethodsモジュールのメソッドを使えるようになりました。

The URL to Trackback this entry is:
http://dev.ariel-networks.com/Members/inoue/ruby-is-hard/tbping
Add comment

You can add a comment by filling out the form below. Plain text formatting.

(Required)
(Required)
(Required)
This helps us prevent automated spamming.
Captcha Image


Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.