【Ruby】定数について

スポンサーリンク

Ruby の定数についてまとめました。

定数

アルファベット大文字 ([A-Z]) で始まる識別子は定数です。 定数の定義と初期化は値の代入によって行われ、定数は定義されたクラス/モジュール内や継承関係がある場合は定数名のみで参照できます。

すべての定数は、ファイルシステムのようにツリー状に配置されていて、クラス/モジュールがディレクトリで、定数がファイルと考えると分かり易いです。

module A
  X = 'classAの定数X'
  class B
    Y = 'classBの定数Y'
    puts X  #=> "classAの定数X"
    puts Y  #=> "classBの定数Y"
    p Module.nesting  #=> [A::B, A]
  end
end

定数をメソッドの中では定義することはできません。

class MyClass
  def my_method
    M = 'my_methodの定数'
  end
end
#=> SyntaxError: (irb):14: dynamic constant assignment

一度定義された定数に再び値を代入をすると警告メッセージが出ますが代入した値に代わります。

puts M  #=> "トップレベルの定数M"
M = '再代入'
#=> warning: already initialized constant M
#=> warning: previous definition of M was here
puts M  #=> "再代入"

代入は警告がでますが変更は普通にできてしまいます。変更もさせたくない場合はfreezeを使いましょう。

M << '追加'
#=> "再代入追加"
M.freeze
M << '追加'
RuntimeError: can't modify frozen String

定数 - Rubyリファレンスマニュアル

定数の参照

定義されたクラス/モジュール内や継承関係がある場合は定数名のみで参照できますが、他のクラス/モジュールで定義された定数を参照するには::演算子を使って定数のパスを指定します。トップレベルの定数を確実に参照するにはパスを::から始めると外部の定数を絶対パスで指定できます。

M = 'トップレベルの定数M'
module A
  class B
    M = 'moduleAclassBの定数M'
    puts M  #=> "moduleAclassBの定数M"
  end
end
class C
  M = 'classCの定数M'
  puts M  #=> "classCの定数M"   クラスの定義内
  puts A::B::M  #=> "moduleAclassBの定数M"   外部の同名定数
  puts ::M  #=> "トップレベルの定数M"   トップレベルの同名定数
end

BasicObject を継承したクラス(クリーンルーム)の場合、Stringなどもスコープ外になってしまうため、絶対パスで指定sるう必要があります。

class MyClass < BasicObject
  name = String.new("task")
end
#=> NameError: uninitialized constant MyClass::String

class MyClass < BasicObject
  name = ::String.new("task")
end
#=> "task"

クラスとモジュールの定数

定数のパスという表現をしましたが、実はクラスもモジュールも定数です。生成されたクラスオブジェクトが指定したクラス名の定数に代入されているわけです。クラス名を参照することは文法上は定数を参照しているだけという事が理解できれば、::演算子で表すパスも全てクラスやモジュールを含めてただの定数だということが分かります。

次の class 定義はどちらも同等の意味を持ちます。

class Project < ActiveRecord::Base
end

Project = Class.new(ActiveRecord::Base)

module 定義も同様です。

module Admin
end

Admin = Module.new

定数関連のメソッド

定数を操作するためのメソッドには以下のようなものが存在します。

メソッド 機能
Module.constants このメソッドを呼び出した時点で参照可能な定数名の配列を返す
Module#constants モジュールまたはクラスで定義されている定数名の配列を返す
Module#const_defined? 引数で指定した名前の定数が定義されていればTrueを返す
Module#const_get 引数で指定した名前の定数の値を返す
Module#const_set 引数で指定した名前と値の定数を定義する
Module#const_missing 定義されていない定数を参照したときに呼びだされる
Module#deprecate_constant (private) 引数で指定した定数を deprecate に設定する
Module#public_constant (private) 引数で指定した定数の可視性を public に変更する
Module#private_constant (private) 引数で指定した定数の可視性を private に変更する
Module#remove_const (private) 引数で指定した定数を取り除いて設定されていた値を返す
CONST_A = 'トップレベルの定数M'
module MyModuleA
  class MyClassB
    CONST_B = 'moduleAclassBの定数M'
  end
end
class MyClassC
  CONST_C = 'classCの定数M'
  Module.constants.grep(/My|CONST/)  #=> [:CONST_C, :CONST_A, :MyModuleA, :MyClassC]
end

MyModuleA::MyClassB.constants(false)  #=> [:CONST_B]
MyClassC.const_defined?(:CONST_C)  #=> true
MyClassC.const_set(:CONST_D, '追加した定数')  #=> "追加した定数"
MyClassC.const_get(:CONST_D)  #=> "追加した定数"
MyClassC.constants(false)  #=> [:CONST_C, :CONST_D]

class Hoge
  Bar = '非推奨の定数'
  deprecate_constant(:Bar)
  Foo = 'public定数'
  public_constant(:Foo)
  Piyo = 'private定数'
  private_constant(:Piyo)

  def self.const_missing(key)
    "#{key}の定数はありません"
  end
end

Hoge::Fuga  #=> "Fugaの定数はありません"
Hoge::Bar  #=> "非推奨の定数"
# warning: constant Hoge::Bar is deprecated
Hoge::Foo  #=> "public定数"
Hoge::Piyo  #=> NameError: private constant Hoge::Piyo referenced

class Hoge
  puts Piyo  #=> private定数
  remove_const(:Piyo)
  puts Piyo  #=> Piyoの定数はありません
end