🐘

【Rails】class_attribute について

2024/04/07に公開

概要

Rails の ActiveSupport の機能の一つである、 class_attribute について書きました!
定数、クラス変数との違いについて触れているので、参考になれば嬉しいです!

https://railsguides.jp/active_support_core_extensions.html#class-attribute

クラスに属性をもたせることができる

class Oya
  class_attribute(:some_attribute, default: 1)
end

Oya.some_attribute
=> 1

メソッド内で使うときはこんな感じ

class Oya
  class_attribute(:some_attribute, default: 1)

  def some_method
    self.some_attribute
  end
end

Oya.new.some_method
=> 1

instance_accessor オプションを false にするとインスタンスレベルでの呼び出しや変更ができなくなります。

class Oya
  class_attribute(:some_attribute, default: 1, instance_accessor: false)

  def some_method
    self.some_attribute
  end
end

Oya.new.some_attribute
NoMethodError: undefined method `some_attribute' for #<Oya:0x0000aaaaed86b260>

Oya.new.some_method
NoMethodError: undefined method `some_attribute' for #<Oya:0x0000aaaaed43c2e8>

定数と違い、メソッド内で変更できる

定数はメソッド内で値を変更するコードが存在すると、定義時に dynamic constant assignment が発生します。

class Oya
  SOME_CONSTANT = 1

  def some_method
    SOME_CONSTANT = 2 # dynamic constant assignment が発生
  end
end

class_attribute ではメソッド内での上書きができます。

class Oya
  class_attribute(:some_attribute, default: 1)

  def some_method
    self.some_attribute = 2
  end

  def some_method2
    self.some_attribute
  end

  def some_method3
    self.class.some_attribute
  end
end

# インスタンスから呼び出される attribute のみ値が変更される。
oya = Oya.new
oya.some_method
=> 2
oya.some_method2
=> 2

# もとのクラスの attribute の値は変わらない。
Oya.some_attribute
=> 1

サブクラスで値を変更してもスーパークラスに影響を与えない

スーパークラスの属性をサブクラスに継承したい場合、クラス変数(@@)を使うことができます。

しかし、クラス変数はサブクラス内で変更された際に、スーパークラスのクラス変数も変更してしまうため、予期せぬバグが発生しやすいです。

余談ですがクラス変数を呼び出すにはゲッター(self.some_attribute)が必要です。

class Oya
  @@some_attribute = 1

  def self.some_attribute
    @@some_attribute
  end
end

class Ko < Oya
  def change_attribute
    @@some_attribute = 2
  end
end

Oya.some_attribute
=> 1

Ko.some_attribute
=> 1

Ko.new.change_attribute
=> 2
# スーパークラスまで値が変わってしまう。
Oya.some_attribute
=> 2

class_attribute なら、サブクラスで値を変更したとしても、スーパークラスには影響を与えません。

class Oya
  class_attribute(:some_attribute, default: 1)
end

class Ko < Oya
  def change_attribute
    self.some_attribute = 2
  end
end

Oya.some_attribute
=> 1

Ko.some_attribute
=> 1

Ko.new.change_attribute
=> 2
# スーパークラスは変わらない。
Oya.some_attribute
=> 1

まとめ

サブクラスでのメソッド内での変更 呼び出し時のゲッター
クラス変数 スーパークラスに影響する 必要
定数 定義時エラーする -
class_attribute スーパークラスに影響しない 不要

Discussion