よたらぼ
自分の興味の赴くままにIT技術系のネタを取りとめもなくメモっています。
Ruby言語やLinuxのネタが多いです。

November 27, 2006 [おもひで]

[Rails] Rails 1.2RC1対応

Rails 1.2RC1が出たのでRuby-GetText周り(CVS)の確認をしてみた。

大体は動いてるみたいなんだけど、テストが失敗するね。1.2リリースまでには直したいところだけど間に合うかな。すでにチャレンジされている方、パッチ歓迎です;)。

#DEPRECATIONのメッセージだけは直しました。

[Rails] ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy

Rails 1.2からは@cookies, @flash, @headers, @params, @request, @response, @sessionの各変数に直接アクセスするのがDeprecatedになっていて、そういったコードがあるとDEPRECATION WARNINGというメッセージを出すようになった。

それにしても、インスタンス変数にアクセスするだけでWarningを出す、なんて、どういう風に実装しているんだろう。インスタンス変数へのアクセス時にフックなんてできたっけな?と思ってちょっと調べてみたので、メモ。

本当の実装は、

activesupport-1.3.1.5618/lib/active_support/deprecation.rb

actionpack-1.12.5.5618/lib/action_controller/base.rb

activesupport-1.3.1.5618/lib/active_support/core_ext/module/attr_internal.rb

を見てほしいんだけど、それを超簡略化すると以下のような感じ。

# activesupport-1.3.1.5618/lib/active_support/deprecation.rbから抜粋
class DeprecatedInstanceVariableProxy
  instance_methods.each { |m| undef_method m unless m =~ /^__/ }
  
  def initialize(instance, method, var = "@#{method}")
    @instance, @method, @var = instance, method, var
  end
  
  private
  def method_missing(called, *args, &block)
    warn caller, called, args
    target.__send__(called, *args, &block)
  end
  
  def target
    @instance.__send__(@method)
  end
  
  def warn(callstack, called, args)
    puts ("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}")
  end
end
 
# 呼出側:actionpack-1.12.5.5618/lib/action_controller/base.rbから抜粋
class Test
  def initialize
    @_headers = []
    @headers = DeprecatedInstanceVariableProxy.new(self, "headers")
  end
  def headers
    #実際は、attr_internalでセットしている。以下を参照
    #activesupport-1.3.1.5618/lib/active_support/core_ext/module/attr_internal.rb
    @_headers
  end
end
  
# わかりやすいようにサブクラスで実験
class Test2 < Test
  def initialize
    super
    @headers[0] = 1  #ここ
  end
end
  
p Test2.new
実行結果:
@headers is deprecated! Call headers.[]= instead of @headers.[]=. Args: [0, 1]
@headers is deprecated! Call headers.inspect instead of @headers.inspect. Args: []
#<Test2:0x2aaaaab0a700 @headers=[1], @_headers=[1]>

ポイントは、DEPRECATEDなインスタンス変数にアクセスするというのはつまり、DEPRECATEDなインスタンス変数の*インスタンスメソッドに*アクセスする、ということ。

簡単に説明すると、まず、@headersの代わりに@_headersを用意。headersメソッドはこちらを使うようになる(attr_internalで設定)。
@headersは今までのインスタンス(ここでは配列)をそのまま使うのでは無く、DeprecatedInstanceVariableProxyを噛ませて、そのインスタンスメソッドにアクセスさせるようにする。
DeprecatedInstanceVariableProxyの中身はどうなっているかというと、インスタンスメソッドは全てundef。インスタンスメソッドにアクセス(ここでは@headers[0] = 1)されてきたらmethod_missing経由でWarningを出力し、元の(self.headers)メソッドを呼び出すようにする。
#なお、実行結果が2行出てしまっているのはご愛嬌(inspectのWarningのやつね)

なるほど、かしこいのう。というよりも、こういう(あると便利なんだけど、遅くなりそうで、かつ、あまりアプリ・ライブラリとしての本筋には関係のない)実装をスクリプト言語で躊躇無くしてしまうのがいろんな意味ですごい。
#もともとRailsは遅くなりそうな実装テンコモリだけどねー。


編集