Inheritable Attributesについて

元ネタ:http://railstips.org/2006/11/18/class-and-instance-variables-in-ruby
Ruby on Railsでの実装:http://www.koders.com/ruby/fid1F286C43B89819E4D009281393E1ECAAC371B700.aspx?s=def#L10

クラス変数とクラスのインスタンス変数の中間的なもの。

おさらい:クラス変数の挙動

class Polygon
  @@sides = 10
  def self.sides
    @@sides
  end
end

puts Polygon.sides # => 10

class Rectangle < Polygon
  @@sides = 4
end

puts Polygon.sides # => 4

クラス変数はこのように親/サブクラスで共通なので、サブクラスでの変更が親クラスにも影響してしまう。

おさらい:クラスのインスタンス変数の挙動

class Polygon
  class << self; attr_accessor :sides end
  @sides = 8
end

class Triangle < Polygon
  @sides = 3
end

puts Triangle.sides # => 3
puts Polygon.sides # => 8

その点、クラスのインスタンス変数は親クラスとサブクラスで異なる値を持てる。

ただし、これには罠があって…

class Octogon < Polygon; end
puts Octogon.sides # => nil

継承したクラスには値は伝搬されない。

Inheritable Attributes

ここまでのまとめ兼Inheritable Attributesの立ち位置

クラス変数 クラスのインスタンス変数 Inheritable Attributes
親クラスの値を継承する ×
親クラス/サブクラス間で異なる値を持つ ×

まーなんか大げさな名前はついてるけど、実際はinherited()呼び出しで「クラスのインスタンス変数」をコピーする様にしているだけ。

という訳でリンク先を参考に作ったのがこれ:
http://websvn.nyaxtstep.com/viewvc.cgi/sandpit/ruby/inheritable_attr/

module InheritableAttr
  
  module ClassMethods

    def inheritable_attr
      @inheritable_attr ||= {}
    end

    def get_inheritable_attr(key)
      inheritable_attr[key]
    end

    def set_inheritable_attr(key, val)
      inheritable_attr[key] = val
    end

  private
    def inherited(child)
      child.instance_variable_set('@inheritable_attr', inheritable_attr.dup)
    end
  end

  def self.included(base)
    base.extend(ClassMethods)
  end

end

使い方はこんな感じ:

require 'inheritable_attr'

describe InheritableAttr do

  it "should handle inheritance correctly" do
    class Lang
      include InheritableAttr
      
      set_inheritable_attr(:ringo, "unknown")
    end

    Lang.get_inheritable_attr(:ringo).should == "unknown"

    class Unknown < Lang; end

    Unknown.get_inheritable_attr(:ringo).should == "unknown"

    class English < Lang
      set_inheritable_attr(:ringo, "Apple")
    end

    English.get_inheritable_attr(:ringo).should == "Apple"

    class Japanese < Lang
      set_inheritable_attr(:ringo, "ringo")
    end

    Japanese.get_inheritable_attr(:ringo).should == "ringo"

    English.get_inheritable_attr(:ringo).should == "Apple"
    Unknown.get_inheritable_attr(:ringo).should == "unknown"
    Lang.get_inheritable_attr(:ringo).should == "unknown"
  end

end