ゆうなんとかさんの雑記帳的な。

Twitterで踊ったり音ゲーしたりしてるあの名前がよくわからない人が書いてるらしいよ。

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

こんな感じになりました。まあ、いいよね、うん。