JQ Blog

クラス定義①

知っておくべきこと

Rubyにおけるクラスとは

Rubyにおけるクラス定義は「コードを実行する」ということだ。JavaやC#ではクラスを定義してもそのクラスのオブジェクトを生成して、メソッドを呼び出すまで何も起きない。しかし、Rubyでクラスを定義するということはオブジェクトの動作を規定することではなく、コードを実行するということなのだ。

クラスインスタンス変数 & クラス変数

クラスインスタンス変数

1
2
3
class MyClass
  @my_var = 1
end

こういうふうにクラスにもインスタンス変数が定義できる。ここの@my_varはクラス定義の中にあるのでクラスに属している。
だからクラスのオブジェクトのインスタンス変数とは別物ということだ。

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass
  @my_var = 1

  def self.read; @my_var; end
  def write; @my_var = 2; end
  def read; @my_var; end
end

obj = MyClass.new
obj.write
obj.read     # => 2
MyClass.read # => 1

上記のコードには2つのインスタンス変数を定義している。どちらも@my_varという名前だ。しかし、それぞれ異なるスコープに定義されており、別々のオブジェクトに属している。上記のコードのMyClassのオブジェクトのobjMyClassクラスのインスタンス変数の@my_varは呼べない。同じくobjの外側からもobjのインスタンス変数の@my_varが呼べないのだ。
こういうふうにクラスがselfとなる場所に定義されているインスタンス変数をクラスインスタンス変数と呼ぶ。

クラス変数

クラス変数は@@をつけて定義できる。

1
2
3
class C
  @@v = 1
end

これはクラスインスタンス変数とは違う。サブクラスや通常のインスタンスメソッドからもアクセスできる。

1
2
3
4
5
class D < C
  def my_method; @@v; end
end

D.new.my_method # => 1

しかし、クラス変数の使いには少し気をつけないといけないところがある。

1
2
3
4
5
6
7
@@v = 1

class MyClass
  @@v = 2
end

@@v # => 2

@@vの値が変わるのはこのクラス変数がクラスではなく、クラス階層に属しているからだ。@@vmainのコンテキストに定義されているので、mainのクラスであるObjectの全ての子系にも属しているということになり、全部同じクラス変数を共有しているからだ。

特異メソッド

特異メソッド

Rubyでは、特定のオブジェクトにメソッドを追加できる。

1
2
3
4
5
6
7
8
9
str = "just a regular string" # 普通の文字列

def str.title?
  self.upcase == self
end

str.title?                 # => false
str.methods.grep(/title?/) # => ["title?"]
str.singleton_methods      # => ["title?"]

上記のコードはtitle?メソッドを文字列strに追加している。Stringクラスの他のオブジェクトには影響はない。
こうしたあるオブジェクトに特化したメソッドのことを特異メソッドと呼ぶ。

クラスメソッドの真実

クラスメソッドはクラスの特異メソッドである。

1
2
3
4
5
6
7
# 特異メソッドの定義
def MyClass.class_method; end

# クラスメソッドの定義
class MyClass
  def self.class_method; end
end

上記の2つの定義は同じものなのだ。

1
2
3
def object.method
  # メソッドの中身
end

上記のobjectの部分にはオブジェクトの参照、クラス名の定数、selfのいずれかが使える。つまり、クラスメソッドはselfを使った特異メソッドである。

クラスマクロ

Module#attr_*()族のメソッドを使えば、一気にオブジェクトに対するアクセサを生成できる。
Module#attr_readerは読み取り用、Module#attr_writerは書き込み用、Module#attr_accessorは読み書き両用だ。
コードで見てみると

1
2
3
4
5
class Person
end

person = Person.new
person.name # => NoMethodError: undefined method `name' for #<Person:0x007f88d1cbbb58>

NoMethodErrorが出てしまう。

1
2
3
4
5
6
7
8
9
class Person
  def name
    @name
  end
end

person = Person.new
person.name        # => nil
person.name = 'Jo' # => NoMethodError: undefined method `name=' for #<Person:0x007f88d1ede5e8>

nameのメソッドは読み込めるが、アクセスはできない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person
  def name
    @name
  end

  def name=(str)
    @name = str
  end
end

person = Person.new
person.name        # => nil
person.name = 'Jo' # => "Jo"
person.name        # => "Jo"

これでインスタンス変数の@nameにアクセスできる。
これはこうもかける。

1
2
3
4
class Person
  attr_reader :name
  attr_writer :name
end

そして、

1
2
3
4
5
6
7
8
9
10
11
class Person
  attr_accessor :name

  def greeting
    "Hello, #{@name}"
  end
end

person = Person.new
person.name = 'Jo' # => "Jo"
person.greeting    # => "Hello, Jo"

こう使える。
このようなメソッドをクラスマクロと呼ぶ。
クラスマクロはキーワードのように見えるが、クラス定義の中で使えるクラスメソッドである。

&Check

&Checkでのクラスメソッド

formクラスでよくクラスメソッドを使っている。

1
2
3
4
5
6
7
8
9
10
11
class CandidatePanelCsvForm

    ・・・

  def self.model_name
    ActiveModel::Name.new(self, nil, "CandidatePanelCsvForm")
  end

    ・・・

end

&Checkでのクラスマクロ

form, service, usecaseなどで幅広く使われている。(attr_accessor, attr_reader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module Csv
  class ResponseSetsService
    attr_accessor :filer

    def initialize(resource_owner, survey, **args)
      @resource_owner = resource_owner
      @survey         = survey
      @survey_group   = survey.group
      @args           = args
      @filer          = nil
    end

    ・・・

  end
end

module Missions
  class RecipientService
    attr_reader :burden

    delegate :commenter, to: :burden

    def initialize(burden)
      @burden = burden
    end

    ・・・

end

全然関係ないけど気になるやつ

delegate

上記のコードの中でdelegateというのがあったけど、delegateは何物か気になったから調べておく。
delegatehas_one, throughと似たようなもので、toで指名しているオブジェクトからの関係あるモデルを呼び出せるらしい。
上記ではto:burdenを指名しているから、:burdenの関係モデルの中のcommenterを持ってこれる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Qualitative::SurveyGroupComment < ApplicationRecord

  ・・・

  private

  def notify_related_actors
    recipient_service = Missions::RecipientService.new(self)

    ・・・

  end

  ・・・

end

上記のコードでわかるように渡されるburdenQualitative::SurveyGroupCommentのオブジェクトである。
だから、delegateを使ってQualitative::SurveyGroupCommentオブジェクトの関係モデルのcommenterオブジェクトを呼び出すことができるということだ。

1
Missions::RecipientService.new(self).commenter

のように使える。

次の記事

第4章 クラス定義の残った分量の

  • 特異クラス
  • アラウンドエイリアス

あと、

  • 第5章「コードを記述するコード」