Rubyの変数とスコープをじっくり検証する

あれ、変数が使えない?

ラクティスでコードを書いているときに変数が定義されてないよーというエラーが出てしまうことがありました。
例えば、こんなコードがエラーになります。

%w[月 火 水 木 金 土 日].each do |day|
  week = {}  
  holiday = %w[土 日]
  if holiday.include?(day)
    week[day] = '休日'
  else
    week[day] = '平日'
  end
end

puts week

エラーメッセージがでる

`<main>': undefined local variable or method `week' for main:Object (NameError)

puts week
     ^^^^

これはweekをブロックの外に出せば正しく動きます。

week = {}  
%w[月 火 水 木 金 土 日].each do |day|  
  holiday = %w[土 日]
  if holiday.include?(day)
    week[day] = '休日'
  else
    week[day] = '平日'
  end
end

puts week

正常に動きました

{"月"=>"平日", "火"=>"平日", "水"=>"平日", "木"=>"平日", "金"=>"平日", "土"=>"休日", "日"=>"休日"}

原因はブロック内で変数を定義していたのでブロック外では使えないということでした。
情報を整理するために、一度変数とそのスコープをじっくり検証してみたいと思います。

変数とスコープ検証

ローカル変数

local_variable = 1

基本の変数です。
スコープは宣言したブロック、メソッド定義、クラス/モジュール定義の中。

インスタンス変数

@instance_variable = 1
クラス定義の中で宣言します。
スコープはnewしたインスタンス内なので別のメソッドからも呼べます。

class Practice
  def instance_variable
    @instance_variable
  end
  
  def instance_variable=(value)
    @instance_variable = value
  end
end

practice =Practice.new
practice.instance_variable = 1
puts practice.instance_variable
#=> 1

初期値を与えようとしてこう書くとnilが返ってきます。

class Practice
  @instance_variable = 1
  def instance_variable
    @instance_variable
  end  
end

practice =Practice.new
p practice.instance_variable
#=> nil

これはクラス定義の最中に定義されたインスタンス変数はクラスが持つインスタンス変数になり、newされたインスタンスインスタンス自身が持つインスタンス変数になるから。要はそれぞれ別のオブジェクトという事です。

初期値を与えたいのであればinitializeの中で指定することができます。

class Practice
  def initialize
    @instance_variable = 1
  end

  def instance_variable
    @instance_variable
  end  
end

practice =Practice.new
p practice.instance_variable
#=> 1

クラス変数

クラス変数はクラス定義の中で定義されます。
スコープはクラス内とインスタンス内、さらにサブクラスからもアクセスできます。
@@class_variable = 1

class Practice
  @@class_variable = 2
  def class_variable
    @@class_variable
  end
end

practice =Practice.new
p practice.class_variable
#=> 2

クラスメソッドを使えばクラスから直接参照もできます。

class Practice
  @@class_variable = 2
  def self.class_variable
    @@class_variable
  end  
end

p Practice.class_variable
#=> 2

クラス変数は別のインスタンスであっても同じオブジェクトを参照します。 クラス自身も同じオブジェクトを参照します。

class Practice
  def class_variable
    @@class_variable
  end

  def class_variable=(value)
    @@class_variable = value
  end

  def self.class_variable
    @@class_variable
  end

  def self.class_variable=(value)
    @@class_variable = value
  end
end

a = Practice.new
b = Practice.new

a.class_variable = 1
b.class_variable = 2
Practice.class_variable = 3

p a.class_variable
p b.class_variable
p Practice.class_variable

#=> 3
#=> 3
#=> 3

モジュール変数

モジュールの中でクラス変数を定義すると、クラスをまたいで参照することができます(モジュール変数)
@@module_valiable = 1

module Practice_module
  @@module_variable = 4
  class Module_class1
    def module_variable
        @@module_variable
    end

    def module_variable=(value)
      @@module_variable = value
    end
  end

  class Module_class2
    def module_variable
        @@module_variable
    end

    def module_variable=(value)
      @@module_variable = value
    end
  end
end

include Practice_module
a = Module_class1.new
b = Module_class2.new
a.module_variable = 5
b.module_variable = 6

p a.module_variable
p b.module_variable
#=> 6
#=> 6

グローバル変数

どこからでも参照できる。乱用してはいけない。
$global

クラスインスタンス変数

先ほどインスタンス変数のところでちらっと出てた、クラスの中かつメソッドの外で宣言したインスタンス変数です。
クラスオブジェクト自身が持っているインスタンス変数という扱いです
@instance_valiable

class Practice
  @instance_variable = 1
  def instance_variable
    @instance_variable
  end
  puts @instance_variable
end
#=> 1

クラスメソッドを使えばクラス自身から参照できます。

class Practice
  @instance_variable = 1
  def self.instance_variable
    @instance_variable
  end  
end

p Practice.instance_variable
#=> 1

クラス定義の最中ならばputsもできます。

class Practice
  @instance_variable = 1
  def instance_variable
    @instance_variable
  end
  puts @instance_variable
end
#=> 1

以上、変数とスコープのまとめでした。