Ruby Gold 認定試験の Ver 3 の勉強時に学んだことを備忘録として残しておく

[前回までの記事]

環境

  • ruby: 3.1.2

キーワード引数

キーワード引数で a: のようなデフォルト値を指定しない場合、構文エラーにはならないが、呼び出し時に指定しなかった場合に ArgumentError になる

class KeywordArgument
  def foo(a:, b: 'bbb')
    puts "a: #{a}, b: #{b}"
  end
end

keyword_argument = KeywordArgument.new
keyword_argument.foo(a: 'aaa', b: 'BBB')
#=> a: aaa, b: BBB

keyword_argument.foo(a: 'aaa')
#=> a: aaa, b: bbb

keyword_argument.foo
#=> ./keyword_argument.rb:2:in `foo': missing keyword: :a (ArgumentError)
#=>	from ./keyword_argument.rb:8:in `<main>'

グローバル変数, クラス変数, インスタンス変数

グローバル変数

グローバル変数のため全体で同じ変数を参照

class GlobalArgument
  $count = 0

  def increment
    $count = $count + 1

    puts $count
  end

  def self.increment
    $count = $count + 1

    puts $count
  end
end

class ExtendGlobalArgument < GlobalArgument
  def increment
    $count = $count + 2

    puts $count
  end
end

global_argument = GlobalArgument.new
global_argument2 = ExtendGlobalArgument.new

global_argument.increment
#=> 1
global_argument2.increment
#=> 3
global_argument.increment
#=> 4
global_argument2.increment
#=> 6

クラス変数

クラスごとに変数が管理される

class ClassVariable
  @@count = 0

  def increment
    @@count = @@count + 1

    puts @@count
  end

  def self.increment
    @@count = @@count + 1

    puts @@count
  end
end

class ExtendClassVariable < ClassVariable
  def increment
    @@count = @@count + 2

    puts @@count
  end
end

class_argument = ClassVariable.new
class_argument2 = ExtendClassVariable.new

class_argument.increment
#=> 1
class_argument2.increment
#=> 3
class_argument.increment
#=> 4
class_argument2.increment
#=> 6

インスタンス変数

グローバル変数、クラス変数と同じ要領で書くとエラー

class InstanceVariable
  @count = 0

  def increment
    @count = @count + 1

    puts @count
  end

  def self.increment
    @count = @count + 1

    puts @count
  end
end

class ExtendInstanceVariable < InstanceVariable
  def increment
    @count = @count + 2

    puts @count
  end
end

instance_variable = InstanceVariable.new
instance_variable2 = ExtendInstanceVariable.new

instance_variable.increment
#=> ./instance_variable.rb:5:in `increment': undefined method `+' for nil:NilClass (NoMethodError)
#=> @count = @count + 1

accessor もしくは initialize メソッドでの初期化が必要
※インスタンスごとに変数が管理される

class InstanceVariable
  def initialize
    @count = 0
  end

  def increment
    @count = @count + 1

    puts @count
  end

  def self.increment
    @count = @count + 1

    puts @count
  end
end

class ExtendInstanceVariable < InstanceVariable
  def increment
    @count = @count + 2

    puts @count
  end
end

instance_variable = InstanceVariable.new
instance_variable2 = ExtendInstanceVariable.new

instance_variable.increment
#=> 1
instance_variable2.increment
#=> 2
instance_variable.increment
#=> 2
instance_variable2.increment
#=> 4

singleton を include した場合は同一インスタンスを返却するようになる

require 'singleton'

class SingletonInstanceVariable
  include Singleton

  def initialize
    @count = 0
  end

  def increment
    @count = @count + 1

    puts @count
  end

  def self.increment
    @count = @count + 1

    puts @count
  end
end

class ExtendSingletonInstanceVariable < SingletonInstanceVariable
  def increment
    @count = @count + 2

    puts @count
  end
end

# SingletonInstanceVariable.new は使えない => NoMethodError
instance_variable = SingletonInstanceVariable.instance
instance_variable2 = ExtendSingletonInstanceVariable.instance
instance_variable3 = SingletonInstanceVariable.instance

instance_variable.increment
#=> 1
instance_variable2.increment
#=> 2
instance_variable3.increment
#=> 2
instance_variable.increment
#=> 3
instance_variable2.increment
#=> 4
instance_variable3.increment
#=> 4

引数の異なるメソッドを継承先のクラスで定義した際の super の挙動

super 指定時に () を省略した場合は super と同じ指定がされたものと見なされるため Arguments Error となる

class Foo
  def foo(a, b)
    puts "Foo#foo a=#{a}, b=#{b}"
  end
end

class Bar < Foo
  def foo(a)
    super
    puts "Bar#foo a=#{a}"
  end
end

foo = Foo.new
foo.foo('A', 'B')
#=> Foo#foo a=A, b=B

bar = Bar.new
bar.foo('aaa')
#=> ./inheritance_method.rb:2:in `foo': wrong number of arguments (given 1, expected 2) (ArgumentError)
#=> from ./inheritance_method.rb:9:in `foo'
#=> from ./inheritance_method.rb:19:in `<main>'

デフォルト値を指定してある場合は呼び出せる

class Foo
  def foo(a, b = 'BBB')
    puts "Foo#foo a=#{a}, b=#{b}"
  end
end

class Bar < Foo
  def foo(a)
    super
    puts "Bar#foo a=#{a}"
  end
end

foo = Foo.new
foo.foo('A', 'B')
#=> Foo#foo a=A, b=B

bar = Bar.new
bar.foo('aaa')
#=> Foo#foo a=aaa, b=BBB
#=> Bar#foo a=aaa

もしくは、super に渡す引数を指定する

class Foo
  def foo(a, b)
    puts "Foo#foo a=#{a}, b=#{b}"
  end
end

class Bar < Foo
  def foo(a)
    super(a, 'BB')
    puts "Bar#foo a=#{a}"
  end
end

foo = Foo.new
foo.foo('A', 'B')
#=> Foo#foo a=A, b=B

bar = Bar.new
bar.foo('aaa')
#=> Foo#foo a=aaa, b=BB
#=> Bar#foo a=aaa

method_missing

該当のメソッドが存在しなかった場合に呼び出されるメソッド

module M
  def method_missing(id, *args)
    puts "C#method_missing"
  end
end

class A
  include M

  def method_missing(id, *args)
    puts "A#method_missing"
  end
end

class B < A
  def method_missing(id, *args)
    puts "B#method_missing"
  end
end

obj = B.new
obj.test_method
#=> "B#method_missing"

Object クラスに定義したメソッドについて

クラス定義に使用する Class クラスは、 Object クラスの子孫にあたるため Object クラスに定義したメソッドは呼び出し可能

[Class クラスの継承リスト]
Class < Module < Object < Kernel < BasicObject

class Object
  def foo
    puts 'Object#foo'
  end
end

class Foo
  def foo
    super
    puts 'Foo#foo'
  end
end

class Bar < Foo
  def foo
    super
    puts 'Bar#foo'
  end
end

bar = Bar.new
bar.foo
#=> Object#foo
#=> Foo#foo
#=> Bar#foo

定数の探索範囲

module M
  CONSTANT_A = 'A'.freeze
end

class A
  include M

  CONSTANT_A = 'AAA'.freeze

  def foo
    puts M::CONSTANT_A
    puts CONSTANT_A
  end
end

class B < A
  CONSTANT_A = 'BBB'.freeze

  def foo
    super
    puts CONSTANT_A
  end
end

class C < B
  CONSTANT_C = 'CCC'.freeze

  def foo
    super

    puts CONSTANT_A
  end

  class D
   CONSTANT_D = 'DDD'.freeze

   def foo
    puts CONSTANT_C

    #  puts CONSTANT_A
    #=> uninitialized constant C::D::CONSTANT_A (NameError)
    #=> Did you mean?  C::D::CONSTANT_D
    #=>                C::CONSTANT_C

    # puts ::CONSTANT_A
    #=> uninitialized constant CONSTANT_A (NameError)

    super
    #=> super: no superclass method `foo' for #<C::D:0x000000010502f840> (NoMethodError)
    #=> Did you mean?  for

    puts ::A::CONSTANT_A
    puts ::B::CONSTANT_A
   end
  end
end

a = A.new
b = B.new
c = C.new
d = C::D.new

a.foo
#=> A
#=> AAA
b.foo
#=> A
#=> AAA
#=> BBB
c.foo
#=> A
#=> AAA
#=> BBB
#=> BBB
d.foo
#=> CCC
#=> AAA
#=> BBB