Module内でattr_readerを使う

module Attr
  @cls_ins_val = "default cls_ins_val"
  @@cls_val    = "default cls_val"

  attr_reader :cls_ins_val, :cls_val

  class << self
    def get_cls_ins_val
      @cls_ins_val
    end

    def set_cls_ins_val(val)
      @cls_ins_val = val
    end

    def get_cls_val
      @@cls_val
    end

    def set_cls_val(val)
      @@cls_val = val
    end
  end
end

自分で定義したゲッターもセッターも問題ありません。デフォルト値の取得も出来ています。

test "class instance value" do
  assert_equal "default cls_ins_val", Attr.get_cls_ins_val
  Attr.set_cls_ins_val("after cls_ins_val")
  assert_equal "after cls_ins_val", Attr.get_cls_ins_val

  assert_raise NoMethodError do Attr.cls_ins_val end
end

test "class value" do
  assert_equal "default cls_val", Attr.get_cls_val
  Attr.set_cls_val("after cls_val")
  assert_equal "after cls_val", Attr.get_cls_val

  assert_raise NoMethodError do Attr.cls_val end
end

しかし、attr_reader :cls_ins_val, :cls_valの部分がうまく機能していません。

assert_raise NoMethodError do Attr.cls_ins_val end
assert_raise NoMethodError do Attr.cls_val end

attr_accessorではクラス変数は参照できない

クラスメソッドとして定義するには、下記のように書きます。

module Attr2
  @cls_ins_val = "default cls_ins_val"
  @@cls_val    = "default cls_val"

  class << self
    attr_accessor :cls_ins_val, :cls_val
  end
end

この場合だと、クラス変数のデフォルト値はnilになっています。attr_accessorでは、クラス変数を受け取る事ができていません。

test "class instance value" do
  assert_equal "default cls_ins_val", Attr2.cls_ins_val
  Attr2.cls_ins_val = "after cls_ins_val"
  assert_equal "after cls_ins_val", Attr2.cls_ins_val
  assert_equal "after cls_ins_val", Attr2.cls_ins_val
end

test "class value" do
  assert_nil Attr2.cls_val # "default cls_val"ではない
  Attr2.cls_val = "after cls_val"
  assert_equal "after cls_val", Attr2.cls_val
  assert_equal "after cls_val", Attr2.cls_val
end

同じ名前のクラス変数と、クラスインスタンス変数で確かめてみます。

module Attr3
  @cls_val     = "default @cls_val"
  @@cls_val    = "default @@cls_val"

  class << self
    attr_accessor :cls_val
  end
end
assert_equal     "default @cls_val",  Attr3.cls_val
assert_not_equal "default @@cls_val", Attr3.cls_val

クラスインスタンス変数の方が参照されていますね。これは、クラスにattr_accessorを使った時と同じで下記のように書かれていると推測できます。

module Attr3
  @cls_val = "default @cls_val"

  class << self
    def cls_val
      @cls_val
    end

    def cls_val=(val)
      @cls_val = val
    end
  end
end

ゲッターの中でcls_valの前に付いている@は2つではなく、1つです。なのでインスタンス変数を返そうとします。ですが、クラスメソッドですのでレシーバーはAttr3クラスです。なのでインスタンス変数ではなく、クラスインスタンス変数が返ります。

クラス内でincludeする

クラス内でincludeした場合はどうなるか見てみましょう。

module Attr2
  @cls_ins_val = "default cls_ins_val"
  @@cls_val    = "default cls_val"

  class << self
    attr_accessor :cls_ins_val, :cls_val
  end
end

class Include2
  include Attr2
end

アクセサはNoMethodErrorになります。

test "class value" do
  obj = Include2.new
  assert_raise NoMethodError do obj.cls_ins_val end
  assert_raise NoMethodError do obj.cls_val end

  assert_raise NoMethodError do Include2.cls_ins_val end
  assert_raise NoMethodError do Include2.cls_val end
end

下記のようにattr_accessorをクラスメソッドとして書かなくても同じです。自分で定義したget_cls_ins_valなども、クラスからでもインスタンスからでもNoMethodErrorになります。

module Attr
  @cls_ins_val = "default cls_ins_val"
  @@cls_val    = "default cls_val"

  attr_reader :cls_ins_val, :cls_val

  class << self
    def get_cls_ins_val
      @cls_ins_val
    end

    def set_cls_ins_val(val)
      @cls_ins_val = val
    end

    def get_cls_val
      @@cls_val
    end

    def set_cls_val(val)
      @@cls_val = val
    end
  end
end

class Include
  include Attr
end

moduleをincludeしてインスタンスメソッドを生成

クラスにincludeした時にインスタンスメソッドとしたい場合は、モジュールメソッドにselfを付けないでおきます。selfを付けないのは、クラスに直接インスタンスメソッドを定義する時と同じですね。クラスメソッドにした時の挙動が違うのです。

module Attr5
  @cls_ins_val = "default cls_ins_val"
  @@cls_val    = "default cls_val"

  def get_cls_ins_val
    @cls_ins_val
  end

  def set_cls_ins_val(val)
    @cls_ins_val = val
  end

  def get_cls_val
    @@cls_val
  end

  def set_cls_val(val)
    @@cls_val = val
  end
end

class Include5
  include Attr5
end

クラスインスタンス変数のデフォルト値が取得できていないですね。これはレシーバーがインスタンスだからです。クラスメソッドならレシーバーがクラスなので、クラスインスタンス変数を参照できます。

obj = Include5.new

assert_nil obj.get_cls_ins_val
obj.set_cls_ins_val("after cls_ins_val")
assert_equal "after cls_ins_val", obj.get_cls_ins_val

assert_equal "default cls_val", obj.get_cls_val
obj.set_cls_val("after cls_val")
assert_equal "after cls_val", obj.get_cls_val