Rails 1.2.x の場合
$ ruby script/console >> 1.second => 1 >> 1.second.class => Fixnum
Rails 2.0.x の場合
$ ruby script/console >> 1.second => 1 second # ん? Fixnum じゃなくなったのか!? >> 1.second.class => Fixnum # え! Fixnum なんだ???
というわけで、解析ですが、 その前にまずは Rails 1.2.x 系だとどういう定義 だったかを見てみましょう。
module Time
def seconds
self
end
というのが定義されていて、
class Numeric #:nodoc: include ActiveSupport::CoreExtensions::Numeric::Time include ActiveSupport::CoreExtensions::Numeric::Bytes end
という形で Numeric クラスに include されているだけ、 実にシンプルですね。
そして、Rails 2.0.x の解析です。
結論から言うと、「委譲」によって実現されているという ことになります。
まずは、second の定義を見てみます。
active_support/core_ext/numeric/time.rb
def seconds ActiveSupport::Duration.new(self, [[:seconds, self]]) end
どうやら、1.second で返って来ているのは、 Duration というクラスのオブジェクトらしい。
active_support/duration.rb
class Duration < BasicObject
attr_accessor :value, :parts
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts
end
...
end
で、1.second のレシーバである「1」が @value に入り、 [[:seconds, self]] という配列の配列が @parts に入る。
あと、BasicObject というのを継承していることにも注目。 こいつは実際には、BlankSlate というクラスでして。
active_support/vendor/builder-2.1.2/blankslate.rb
######################################################################
# BlankSlate provides an abstract base class with no predefined
# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
# BlankSlate is useful as a base class when writing classes that
# depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
#
class BlankSlate
class << self
# Hide the method named +name+ in the BlankSlate class. Don't
# hide +instance_eval+ or any method beginning with "__".
def hide(name)
if instance_methods.include?(name.to_s) and
name !~ /^(__|instance_eval)/
@hidden_methods ||= {}
@hidden_methods[name.to_sym] = instance_method(name)
undef_method name
end
end
...中略...
end
instance_methods.each { |m| hide(m) }
end
ってな感じで、__send__, __id__, instance_eval 以外の 全てのメソッドを undef しちゃう hide という クラスメソッドが定義されていて、class 定義の最後に、 instance_methods.each {} によって、全てのメソッドに 対して hide を実行します。
で、いったい何のためにそんなことをしているのかは、 Ruby1.9で追加されたBasicObjectの 解説 を読むと、何のためのクラスなのかがわかると思います。
上のコメントにも書いてありますが、
# BlankSlate is useful as a base class when writing classes that # depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
ってことですね。
というわけで、
class Duration < BasicObject
ってクラスは、__send__, __id__, instance_eval しか 存在しない BasicObject クラスを継承して作られている、 というわけです。
これでやっと、最初の疑問、「.inspect すると 1 second という 文字列が返ってくるのに、.class するとなぜ Fixnum が 返ってくるか」がわかります。
.inspect は、class Duration を読んでもらうとして。
で、.class が呼ばれても、このクラスには .class という メソッドはないわけです。hide しちゃったから。
なので、method_missing が発動されます。
で、class Duration の method_missing がどうなっているか というと、こんな感じ。
def method_missing(method, *args, &block) #:nodoc: value.send(method, *args) end
value すなわち、1.second の 1 に対して、method すなわち .class を *args(この場合は空ですね .class なので)と共に send する、 つまり 1 に委譲して、結局
1.class
が実行されて、
=> Fixnum
となるわけです。
Rails1.2.x の頃のように Fixnum そのままでもそんなに 困ってはいなかったんだが、すごいこだわりですねw