かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Ruby クラス変数・インスタンス変数 のメモ

Ruby お勉強の自分用メモ

インスタンス変数

インスタンスごとに異なる値を持つ変数
@ から始まる変数名で定義する
インスタンス変数はデフォルトでは外部からアクセスすることはできない、
アクセス可能にするにはアクセサの属性を設定する必要がある

class Todo
  def initialize(task, limit)
    # インスタンス変数
    @task = task
    @limit = limit
  end

  def display
    "#{@task}: limit: #{@limit}"
  end
end

todo01 = Todo.new('Taks01', '2019-11-10')
todo02 = Todo.new('Taks02', '2019-11-20')

puts todo01.display
# => "Taks01: limit: 2019-11-10"
puts todo02.display
# => "Taks01: limit: 2019-11-20"

# 外部から直接インスタンス変数にアクセスする際はできない
todo01.@task
# => syntax error

# アクセサが設定されていないのでインスタンス変数に触ることはできない
todo01.task
# => NoMethodError: undefined method `task`

# アクセサを設定
class Todo
  attr_accessor :task
end

# アクセサ (getter) が設定されたインスタン変数にはアクセス可能になる
p todo01.task # => "Taks01"

アクセサ属性

キーワードにインスタンス変数名をシンボルの形式で渡すことで設定

  • attr_accessor ... getter , setter を設定
  • attr_reader ... getter のみを設定
  • attr_writer ... setter のみを設定
class Idol
  attr_reader :name
  attr_writer :type
  attr_accessor :rank

  def initialize(name, type, rank = 100)
    @name = name
    @type = type
    @rank = rank
  end

  def display
    puts "#{@name} (#{@type}) Rank: #{@rank}"
  end
end

aoi = Idol.new('Kiriya Aoi', 'cool')
aoi.display
# => "Kiriya Aoi (cool) Rank: 100"

# attr_accessor は読み書き可能
p aoi.rank # => 100
aoi.rank = 3
p aoi.rank # => 3

# 設定されていない getter, setter を使おうとするとエラー
# attr_reader は getter のみ
p aoi.name
# => "Kiriya Aoi"
aoi.name = 'Hoshimiya Ichigo'
# => NoMethodError: undefined method `name='

# attr_writer は setter のみ
p aoi.type
# => NoMethodError: undefined method `type' for
aoi.type = 'pop'
aoi.display
# => Kiriya Aoi (pop) Rank: 3

attr_accessor の設定は下記の様に独自にgetter, setter を設定するのと同等

class Foo
  def initialize(name = 'foo')
    @name = name
  end
  # getter
  def name
    @name
  end
  # setter
  def name=(val)
    @name = val
  end
end

foo = Foo.new
p foo.name # => "foo"
foo.name = 'bar'
p foo.name # => "bar"

インスタンス変数は値がセットされて始めて生成される

class Idol
  attr_accessor :name

  def my_name_is(name)
    @name = name
  end
end

hoshimiya = Idol.new
# 値がセットされていない状態では存在しない扱い
p hoshimiya.instance_variables
# => []
p hoshimiya.name
# => nil

hoshimiya.my_name_is 'Ichigo'
p hoshimiya.instance_variables
# => [:@name]
p hoshimiya.name
# => "Ichigo"

アクセサの状態とインスタンス変数のアクセス可能な方法の違い

getter, setter の有無によってインスタンス変数にアクセス可能な表記とアクセスできない表記がある

class Hoo
  attr_accessor :read_get_var
  attr_reader :read_only_var
  attr_writer :write_only_var

  def initialize
    @read_get_var   = "has_accessor"
    @read_only_var  = "has_rader"
    @write_only_var = "has_writer"
    @no_accessor    = "no_accessor"
  end

  def call_by_at
    p @read_get_var   # => "has_accessor"
    p @read_only_var  # => "has_rader"
    p @write_only_var # => "has_writer"
    p @no_accessor    # => "no_accessor"
  end

  def call_by_self
    p self.read_get_var  # => "has_accessor"
    p self.read_only_var # => "has_rader"
    # getter メソッドの呼び出しになるのでエラーになる
    p self.write_only_var
    # => NoMethodError: undefined method `write_only_var`
    p self.no_accessor
    # => NoMethodError: undefined method `no_accessor`
  end

  def call_by_no_recever
    p read_get_var  # => "has_accessor"
    p read_only_var # => "has_rader"
    # getter メソッドの呼び出しになるのでエラーになる
    p write_only_var
    # => NameError: undefined local variable or method `write_only_var`
    p no_accessor
    # => NameError: undefined local variable or method `no_accessor`
  end
end

hoo = Hoo.new
# `@変数名` でアクセス
hoo.call_by_at

# `self.変数名` でアクセス
hoo.call_by_self

# `変数名` でアクセス
hoo.call_by_no_recever

メソッド内で @ 無しの変数名でのアクセスは、self の省略形になるので、self.変数名 と同じ結果になる

インスタンス変数はクラスからはアクセスできない

インスタンス変数は、アクセサを設定してもクラスからはアクセスできない

class Bar
  attr_accessor :my_name
  def initialize
    @my_name = 'bar'
  end
end

bar = Bar.new
p bar.my_name # => 'bar'
Bar.my_name
# => NoMethodError: undefined method `my_name' for Bar:Class

クラスメソッドのを作成してもインスタンス変数はインスタンスごとに異なる値を持つのでアクセスできない

class Bar
  attr_accessor :my_name
  def initialize
    @my_name = 'bar'
  end
  def self.my_name
    @my_name
  end
end
p Bar.my_name # => nil

クラス変数

@@ で始まる変数名で定義する
クラス変数は、クラスと子孫クラス・それらのクラスのインスタンス変数で共有される
クラス変数にはアクセサ属性が適応できないので、getter, setter は独自に設定する必要がある

class Foo
  @@class_name = 'foo'
  def display
    puts "#{self.class} class_name is #{@@class_name}"
  end
end

class Bar < Foo
  # instance getter
  def class_name
    @@class_name
  end
  # instance setter
  def class_name=(val)
    @@class_name = val
  end
end

class Baz < Foo
  # class getter
  def self.class_name
    @@class_name
  end
  # class setter
  def self.class_name=(val)
    @@class_name = val
  end
end

foo = Foo.new
bar = Bar.new
baz = Baz.new

foo.display # => Foo class_name is foo
bar.display # => Bar class_name is foo
baz.display # => Baz class_name is foo

bar.class_name = 'bar'
p bar.class_name # => "bar"
foo.display # => Foo class_name is bar
bar.display # => Bar class_name is bar
baz.display # => Baz class_name is bar

p Baz.class_name # => "bar"
Baz.class_name = "xxx"
p Baz.class_name # => "xxx"
p bar.class_name # => "xxx"
foo.display # => Foo class_name is xxx
bar.display # => Bar class_name is xxx
baz.display # => Baz class_name is xxx

# getter, setter を設定してないと外部からインスタンス変数同様アクセスできない
Foo.class_name
# => NoMethodError: undefined method `class_name`
foo.class_name
# => NoMethodError: undefined method `class_name`

クラス変数はインスタンスメソッド内でも定義できるが、メソッドを通って値が渡されるまでクラス変数は設定されない

class Hoo
  def initialize
    @@class_name = 'hoo!'
  end

  def self.class_name
    @@class_name
  end
end

p Hoo.class_name
  # => NameError: uninitialized class variable @@class_name in Hoo
p Hoo.class_variables # => []

hoo = Hoo.new
Hoo.class_name # => "hoo!"
Hoo.class_variables # => [:@@class_name]

継承させた子孫クラスでも同様

class Xoo < Hoo
  @@xoo_var = 'xoo'
  def self.xoo_var
    @@xoo_var
  end
end

# クラス変数に値が設定されていない状態だとエラー
Xoo.class_name
# => <NameError: uninitialized class variable @@class_name in Hoo

xoo = Xoo.new
# 親クラスのクラス変数に子孫クラスで値が設定されれば親クラスでも値が設定されている
p Hoo.class_name # => "hoo!"
p Hoo.class_variables # => [:@@class_name]
p Xoo.class_name # => "hoo!"

# 但し子孫クラスに設定されたクラス変数は親クラスでは参照できない
Xoo.xoo_var # => "xoo"
Hoo.xoo_var
# => NoMethodError: undefined method `xoo_var`

クラスインスタンス変数

クラスインスタンス変数は変数が設定されたクラスからのみ参照できる変数で、クラスのインスタンスや子孫クラスからは参照できない
変数名は @ から始めクラス定義はclass式内のトップレベルまたはクラスメソッド内に定義する (※ 通常のメソッド内で定義するとインスタンス変数になる)
クラスインスタンス変数は、クラス変数と同様でアクセサは自前で実装する必要がある

class CuteIdol
  # クラスインスタンス変数
  @type = 'cute'

  def initialize(name)
    # メソッド内で定義するとインスタンス変数になる
    @name = name
  end

  def say_my_type
    # クラスインスタンス変数はインスタンスからもアクセスできない
    p @type # => nil
  end

  def display
    # クラスからのみアクセスができるので `クラス名.クラスインスタンス変数名` でアクセスができるがgetterが無いと例外が発生する
    # self.type は getter があっても例外になるので注意!
    puts "#{@name} type is #{CuteIdol.type}!"
  end

  def self.display
    # クラスメソッドでは `@変数名` でアクセスが可能
    puts "#{self} Class type is #{@type}"
  end
end

CuteIdol.display
# => CuteIdol Class type is cute
p CuteIdol.instance_variables # => [:@type]

ichigo = CuteIdol.new('Hoshimiya Ichigo')
# クラスのインスタンスからもはアクセスできない
ichigo.say_my_type # => nil
# getter が無いと NoMethodError
ichigo.display
# => NoMethodError: undefined method `type`

class CuteIdol
  def self.type
    @type
  end

  def self.type=(val)
    @type = val
  end
end

ichigo.display
# => Hoshimiya Ichigo type is cute!

クラスメソッド内で定義

class Xoo
  def self.init
    @xoo = 'xoo'
  end

  def self.xoo
    @xoo
  end

  def xoo
    @xoo
  end
end

xoo = Xoo.new
Xoo.init
p Xoo.xoo # => xoo
p xoo.xoo # => nil

class Zoo < Xoo
end

p Zoo.xoo # => nils

クラスインスタンス変数は子孫クラスからもアクセスできない

class Foo
  @class_name = 'foo'

  def self.display
    puts "#{self} name is #{@class_name}"
  end

  def self.class_name
    @class_name
  end
end

class Hoo < Foo
  def self.hi
    p @class_name
  end
end

Foo.display # => "Foo name is foo"
p Foo.class_name # => "foo"
p Foo.instance_variables # => [:@class_name]

Hoo.display # => Hoo name is
Hoo.hi # => nil
p Hoo.class_name # => nil
p Hoo.instance_variables # => []

クラス定数

class式内のトップレベルに大文字から始まる変数を定義するとクラス定数になる
クラス定数は子孫クラスからもアクセス可能
定数なので変更しようとすると warning が発生する

class StarLightStudent
  SCHOOL_NAME = 'StarLight'

  attr_reader :name
  def initialize(name)
    @name = name
  end

  def display
    puts "#{@name} - #{SCHOOL_NAME} Scholl"
  end
end

class Idol < StarLightStudent
  def initialize(name, type)
    super name
    @type = type
  end

  def display
    puts "#{@name} (#{@type}) - #{SCHOOL_NAME} Scholl"
  end
end

p StarLightStudent::SCHOOL_NAME
# => "StarLight"

akari = StarLightStudent.new('Ozora Akari')
akari.display
# => "Ozora Akari - StarLight Scholl"

ichigo = Idol.new('Hoshimiya Ichigo', 'cute')
ichigo.display
# => "Hoshimiya Ichigo (cute) - StarLight Scholl"

クラス定数に外部からアクセスするには getter を独自に作成する必要がある
setter を定義すると warning が発生する

class Week
  DAY_OF_WEEK = 7

  def self.DAY_OF_WEEK
    DAY_OF_WEEK
  end

  def self.DAY_OF_WEEK=(val)
    DAY_OF_WEEK = val
    # => dynamic constant assignment
  end
end

p Week.DAY_OF_WEEK # => 7

# warning が発生するが値の更新は可能
Week::DAY_OF_WEEK = 3
# warning: already initialized constant Week::DAY_OF_WEEK

p Week.DAY_OF_WEEK # => 3

トップレベルに定義された定数は Object のクラス定数として定義される

HOGE = 'hoge!'
p Object::HOGE # => "hoge!"

class Hoge
  HOGE = 'fugafuga'
  def say
    p HOGE # => "fugafuga"
    # トップレベルの定数を参照する
    p Object::HOGE # => "hogehoge!"
  end
end

Hoge.new.say

クラスの定義は、クラスを表す Class クラスのインスタンスを作成し、そのインスタンスを参照するクラス名の定数を作成するので、クラス名と定数名が被っているとエラーになる

HOGE = 'hoge!'

class HOGE
end
# => HOGE is not a class (TypeError)

まとめ

  • インスタンス変数はクラス内のメソッドで使う時は @変数名 にしておけば間違いなさそう
  • クラス内に作れる変数は、外部からアクセスするには基本的にアクセサが必要
    インスタンス変数は attr_ でアクセサを作成することができるが、それ以外は独自に必要なメソッドを作成する必要がある
  • インスタンス変数は @ で始まる変数名でメソッド内に定義
    変数定義は子孫クラスにも引き継がれインスタンスごとに異なる値を持つのでインスタンスからのみアクセス可能
    インスタンス変数は、値がセットされて始めて生成される
  • クラス変数は @@ で始まる変数名で定義・クラス定数は 大文字から始まる変数名で定義
    クラス変数・クラス定数は、クラスと子孫クラス、それらのインスタンスから参照できる
  • クラスインスタンス変数は @ で始まる変数名で class式内のトップレベルまたは、クラスメソッド内に定義 クラスインスタンス変数は、変数の定義されたクラスからのみ参照可能
  • クラス定義はトップレベルに定数として定義される

初めてのRuby

初めてのRuby