odeの開発メモ日記

プログラマーやってます。今までの道のり ポケコンbasic→C(DirectX5 ネットやろうぜ)→Perl(ちょろっと)→Java→C#→Ruby→Android(Java)

class_inheritable_accessorからclass_attributeに移行する際の注意点

rails3.1でclass_inheritable_accessorがdeprecatedになりました。
warningメッセージ
class_inheritable_attribute is deprecated, please use class_attribute method instead. Notice their behavior are slightly different, so refer to class_attribute documentation first
代わりにclass_attributeを使えとのことなのですが単純置換してはだめな場合がありました。

問題点

配列等に値を追加する際に破壊的メソッドで変更すると問題がでます。(API Docに載ってます)
class_inheritable_accessorでは問題ありません。

解決方法

変数名=でコピーされた値を代入する。

サンプル

class Base
  class_attribute :setting
end

class Subclass < Base
end


Base.setting = []
Base.setting                 => []
Subclass.setting             => []

# <<で破壊的に追加すると親クラスにも影響がでる。問題!!
Subclass.setting << :foo
Base.setting                => [:foo]
Subclass.setting            => [:foo]

# += [値]で適用すると問題なし。
Base.setting = []
Subclass.setting += [:foo]  # このタイミングで=が呼ばれてSubclassに読み取り用アクセサメソッドが動的に定義される
Base.setting                => []
Subclass.setting            => [:foo]


# 次のハッシュのテストのために配列テストで定義されたSubclassのsettingメソッドを消す。
Subclass.singleton_class.class_eval do
  remove_possible_method(:setting)
end

# ハッシュの場合はmerge!の代わりにmergeを使う。肝は=メソッドを使うことにあり。=で設定することでSubclassに読み取り用アクセサメソッドが動的に定義される
Base.setting = {}
Subclass.setting.merge!(:foo => "test")
Base.setting                => {:foo => "test"}
Subclass.setting            => {:foo => "test"}

Base.setting = {}
Subclass.setting = Subclass.setting.merge(:foo => "test")
Base.setting                => {}
Subclass.setting            => {:foo => "test"}

実装の違い

  • class_inheritable_accessor
    • 値をクラスインスタンス変数のハッシュで保持。継承する際にコピーしている。
  • class_attribute
    • =メソッドで代入された際にそのクラスに読み取り用アクセサメソッドが動的に定義される。
    • あとは通常のクラス継承の仕組みで動く、Subclassで定義した場合にはそれが呼ばれ、定義してない場合はBaseクラスのメソッドが呼ばれる。
    • そのためパフォーマンスが良くなった。

実行環境

ruby (1.9.2)
rails (3.0, 3.1)