Ruby: attr_accessor を拡張する

クララオンライン グローバルソリューション事業部の平田です。
それでは、早速いきましょう。

Rubyにおけるアクセサ

Ruby では、インスタンス変数へのアクセサを提供するために、自分でアクセサ (セッター ・ゲッター) を書くこと無く、attr_accessor を利用して動的にアクセサメソッドを定義することができます。

これはシンプルで使い勝手の良い機能ですが、後からアクセサの一覧を取得することができないのが辛いところです。

たとえば、変数名と値のハッシュを取り出すために to_h を実装するというのはよくあると思いますが、次のように :foo, :bar を直接書く必要があります。このコードは動作自体に問題はありませんが、:name, :age が2カ所に出てくるため、若干スマートさに欠けてしまいます。

attr_accessor を拡張する

「スマート」なコードを実現するため、今回は attr_accessor を拡張することを考えます。

そもそも attr_accessor は Ruby の標準機能ですが、単なる Module クラスのプライベートなクラスメソッドです。(ただし、Rubyではなく Cのコード で実装されています。) このため、このメソッドを上書きすることで自由に動作を変更することができます。

ここでは、以下のような MyAccessor モジュールを作成しました。任意のクラスでこのモジュールを include することで、再定義されたクラスメソッド attr_accessor とインスタンスメソッド to_h が導入されます。

クラスメソッドとして再定義された attr_accessor では、引数をクラス変数である attributes に追加して、そのまま super で元の attr_accessor を呼び出しています。

なお、何らかの理由で attr_accessor を利用しない場合には、attributes に直接追加することも可能です。

MyAccessorの利用

MyAccessor を利用した例を以下に示します。MyAccessor を include することで、Idol#to_h, Idol.attributes が導入されていることが分かります。

self.included と extend を利用したイディオム

MyAccessor では、self.included と extend を利用したイディオムが使われています。

self.included はモジュールが include された際に自動的に呼び出されるため、メソッド内で extend を呼び出すことによってクラスメソッドを拡張しています。これにより、include するのみで (extend を書くこと無く) インスタンスメソッドとクラスメソッドの両方を拡張することができます。

継承とアクセサ

MyAccessor で拡張されたアクセサの情報と機能は子クラスにも引き継がれます。このため、以下の SchoolIdol クラスについて、SchoolIdol.attributes は :name, :age, :school_name となります。

Classクラス自体の拡張

Classクラス (“Class” という名前が付けられた、クラスを表すためのクラス) 自体の attr_accessor を上書きすることで、特に include することなく同様の機能を実装することは可能で、以下のようなコードになるでしょう。

とはいえ、Classクラス自体を拡張することは混乱する可能性もあるため、あまりおすすめできません。

まとめ

今回はRubyのメタプログラミングの領域に少し触れてみました。

他の言語であれば言語そのものの機能とされるような部分や低レベルからのアクセス (大抵はC言語からのアクセス) が必要な機能も、RubyではRubyで書かれたコードで大きく踏み込むことが可能です。使い方を間違えると混乱を招く可能性がありますが、上手に使ってプログラミングの効率を上げていきましょう。

ちなみに、クララオンライン社内の一部では、attr_accessor を大幅に拡張して利用しています。機会があればそちらも紹介したいと思いますのでご期待ください!

Share on LinkedIn
LINEで送る
Pocket

平田

平田

本業はインフラエンジニアだけど、最近はプログラミングやっています。