attr_reader っぽい何かを作る
さて、今日はattr_readerっぽい何かを作ってみましょう。
まず意味あるのそれ?
さあ?とりあえずメタプログラミングの練習です。少なくとも同じものは実装しないのでいいかなと思ってます。
仕様を決めましょうか
{ name: "Value" }←こんな感じに名前と値をハッシュにして渡すと、"Value"を返すnameという名前のメソッドを作るというものです。名前はそうですね、define_saysとかにしておきましょうか。いいネーミングが思い浮かばないですね。
使い方
class SubClass < SuperClass define_says work: "おうちでこたつむりしながらコードをガシガシ書く" end p SubClass.new.work # "おうちでこたつむりしながらコードをガシガシ書く"
練習なのでシンプルにこんな感じです。
テストを書きましょうか
require 'sub_class' describe SubClass do context 'says_work' do it { expect(SubClass.new.work).to eq "おうちでこたつむりしながらコードをガシガシ書く" } it { expect { SubClass.new.sleep }.to raise_error(NoMethodError) } end end
とりあえず正常な例と例外が飛ぶ例の2つを用意しました。SubClassさんは眠ることを知らないワーカーホリックのようです。
今回使うのはdefine_methodとclass_eval
define_method
名前と中身を渡すとメソッドを作ってくれます。クラスの特異メソッドなのでインスタンスからは使えません。
class_eval
ブロックの中で評価されたことをクラスに反映します。evalという名前こそついていているものの、よくあるevalとは別物でブロックの中には文字列ではなくRubyのコードを書きます。
作戦はこうです。
class_evalしたブロックでクラスにコードを書いて、特異メソッドを定義する
いうのは簡単ですが果たしてどうでしょうか…
動くコードが作れた
class SuperClass def self.define_says(hash) self.class_eval do hash.each {|key, value| define_method(key) { value } end end end end
こんな感じになりました。まあ、いいよね、うん。