Ruby Magicの新しいエピソードへようこそ! 今月号はmetaclasses、更に端を発した議論と開発(こんにちはMaud!).
メタクラスを調べることで、クラスとインスタンスメソッドがRubyでどのように機能するかを学びます。 途中で、明示的な”definee”を渡してメソッドを定義することと、class << self
またはinstance_eval
を使用することの違いを発見してください。 行くぞ!
クラスインスタンスとインスタンスメソッド
Rubyでメタクラスが使用されている理由を理解するために、インスタンスメソッドとクラスメソ
Rubyでは、クラスは他のオブジェクトを作成するための青写真を定義するオブジェクトです。 クラスは、そのクラスの任意のインスタンスで使用可能なメソッドを定義します。
クラス内でメソッドを定義すると、そのクラスにインスタンスメソッドが作成されます。 そのクラスの将来のインスタンスには、そのメソッドが使用可能になります。
1 2 3 4 5 6 7 8 9 10 11 12 |
class User def initialize(name) @name = name end def name @name end end user = User.new('Thijs') user.name # => "Thijs" |
この例では、User
という名前のクラスを作成し、#name
という名前のインスタンスメソッドを使用して、ユーザーの名前を返します。 クラスを使用して、クラスインスタンスを作成し、それをuser
という名前の変数に格納します。 user
はUser
クラスのインスタンスであるため、#name
メソッドが使用可能です。
クラスは、そのインスタンスメソッドをメソッドテーブルに格納します。 そのクラスの任意のインスタンスは、そのインスタンスメソッドにアクセスするためにそのクラスのメソッドテーブルを参照します。
クラスオブジェクト
クラスメソッドは、最初にインスタンスを作成することなく、クラス上で直接呼び出すことができるメソッドです。 クラスメソッドは、定義時に名前の前にself.
を付けることによって作成されます。
クラス自体はオブジェクトです。 定数はクラスオブジェクトを参照するため、そのオブジェクトで定義されたクラスメソッドは、アプリケーションのどこからでも呼び出すことができ
1 2 3 4 5 6 7 8 9 |
class User # ... def self.all end end User.all # => |
self.
-接頭辞で定義されたメソッドは、クラスのメソッドテーブルに追加されません。 それらは代わりにクラスのメタクラスに追加されます。
メタクラス
クラスとは別に、Rubyの各オブジェクトには隠されたメタクラスがあります。 メタクラスはシングルトンであり、単一のオブジェクトに属することを意味します。 クラスの複数のインスタンスを作成すると、それらは同じクラスを共有しますが、すべて別々のメタクラスがあります。
1 2 3 4 5 6 7 8 9 |
thijs, robert, tom = User.all thijs.class # => User robert.class # => User tom.class # => User thijs.singleton_class # => #<Class:#<User:0x00007fb71a9a2cb0>> robert.singleton_class # => #<Class:#<User:0x00007fb71a9a2c60>> tom.singleton_class # => #<Class:#<User:0x00007fb71a9a2c10>> |
この例では、各オブジェクトにクラスUser
がありますが、それらのシングルトンクラスには異なるオブジェクトIdがあり、別々のオブジェクトであるこ
メタクラスにアクセスすることで、Rubyは既存のオブジェクトにメソッドを直接追加することができます。 そうすることで、オブジェクトのクラスに新しいメソッドは追加されません。
1 2 3 4 5 6 7 8 |
robert = User.new("Robert") def robert.last_name "Beekman" end robert.last_name # => "Beekman" User.new("Tom").last_name # => NoMethodError (undefined method `last_name' for #<User:0x00007fe1cb116408>) |
この例では、robert
変数に格納されているユーザーに#last_name
を追加します。 robert
はUser
のインスタンスですが、User
の新しく作成されたインスタンスは、robert
のメタクラスにのみ存在するため、#last_name
メソッドにアクセスできません。
自己とは何ですか?
メソッドを定義してreceiverを渡すと、新しいメソッドがクラスのメソッドテーブルに追加されるのではなく、receiverのメタクラスに追加されます。
1 2 3 4 5 |
tom = User.new("Tom") def tom.last_name "de Bruijn" end |
上記の例では、メソッドを定義するときにtom
を受信者として渡すことによって、#last_name
をtom
オブジェクトに直接追加しました。
これはクラスメソッドの動作方法でもあります。
1 2 3 4 5 6 7 |
class User # ... def self.all end end |
ここでは、.all
メソッドを作成するときに、self
を受信者として明示的に渡します。 クラス定義では、self
はクラス(この場合はUser
)を参照するため、.all
メソッドはUser
のメタクラスに追加されます。
User
は定数に格納されているオブジェクトなので、参照するたびに同じオブジェクトと同じメタクラスにアクセスします。
メタクラスを開く
クラスメソッドはクラスオブジェクトのメタクラス内のメソッドであることを学びました。 これを知って、私たちはあなたが前に見たかもしれないクラスメソッドを作成するいくつかの他のテクニックを見てみましょう。
class<<self
少しスタイルが外れていますが、一部のライブラリではclass << self
を使用してクラスメソッドを定義しています。 この構文トリックは、現在のクラスのメタクラスを開き、それと直接対話します。
1 2 3 4 5 6 7 8 9 10 11 |
class User class << self self # => #<Class:User> def all end end end User.all # => |
この例では、User
のメタクラスにメソッドを追加して、User.all
という名前のクラスメソッドを作成します。 前に見たようにメソッドのreceiverを明示的に渡す代わりに、User
自体ではなくself
をUser
のメタクラスに設定しました。
前に学んだように、明示的な受信者のないメソッド定義は、現在のクラスのインスタンスメソッドとして追加されます。 ブロック内では、現在のクラスはUser
のメタクラス(#<Class:User>
)です。
instance_eval
別のオプションは、instance_eval
を使用することです。 クラスのメタクラスはブロック内で定義されたメソッドを受け取りますが、self
はメインクラスへの参照のままです。
1 2 3 4 5 6 7 8 9 10 11 |
class User instance_eval do self # => User def all end end end User.all # => |
この例では、前と同じようにUser
のメタクラスにインスタンスメソッドを定義しますが、self
はまだUser
を指しています。 通常は同じオブジェクトを指していますが、”default definee”とself
は異なるオブジェクトを指すことができます。
私たちが学んだこと
私たちは、クラスがメソッドを持つことができる唯一のオブジェクトであり、インスタンスメソッドが実際にはオブジェクトのメタクラス上のメソッドであることを学びました。 class << self
は単にself
を入れ替えてメタクラスでメソッドを定義できることを知っていますが、instance_eval
はほとんど同じことをします(ただしself
に触れることはありません)。
メタクラスでは明示的には動作しませんが、Rubyはそれらをボンネットの下で広範囲に使用します。 メソッドを定義したときに何が起こるかを知ることは、Rubyがなぜそのように動作するのか(そしてなぜクラスメソッドの前にself.
を付ける必要があるのか)を理解するのに役立ちます。
読んでくれてありがとう。 あなたが読んだものが好きなら、あなたは私たちが月に一度についての新しい記事を公開するときに電子メールを受け取るためにRuby Magicを購読するこ