RubyまたはRoRで動的にアクセサーを生成・削除する その1
シンプルに考えれば、ファクトリーメソッドパターンで条件にあったクラスのインスタンスを作ればよいだけなのだが、別の解決方法もあったので備忘録として記しておく。
コンストラクト時に動的にアクセサーを設ける
クラスに記述した静的なアクセサーしか使えないと思っていたがそんなことはなかった。10年以上前に考えていた方がいた。
インスタンス変数 @attr を格納先とし、ハッシュとして渡されたアクセサー名から格納する変数とそのアクセスメソッドを生成している。
ruby 2.2.6 以上で実行できた。最新版で確認することは重要である。但し、時間的な生成コストやリスクについては後日調べることにする。
オブジェクトに動的にアクセサーを設ける
前出のブログ記事で紹介されいたのはコンストラクターの引数にハッシュを与えて幾つでも好きなだけアクセサーを加えることだった。実は、前での方法はコンストラクターでなくても可能であり、適当なアクセサー生成メソッド名を決めておけばオブジェクトにアクセサーを加えることもできた。
#! /usr/bin/env ruby #coding: UTF-8 class MyClass # def initialize(h = {}) # @attr = h.dup # h.keys.each do |prop| # self.instance_eval %Q{ # def #{prop.to_s} # @attr["#{prop}".intern] || "" # end # def #{prop.to_s}=(arg) # @attr["#{prop}".intern]= arg # end # } # end # end def make_accessor(h = {}) @attr = h.dup h.keys.each do |prop| self.instance_eval %Q{ def #{prop.to_s} @attr["#{prop}".intern] || "" end def #{prop.to_s}=(arg) @attr["#{prop}".intern]= arg end } end end end #obj = MyClass.new({:test => "dynamic access"}) obj = MyClass.new #puts obj.test obj.make_accessor({:one => "1", :two => "2", :three => "3"}) puts obj.one puts obj.two puts obj.three obj.one = "11" puts obj.one obj.make_accessor({:one => "111"}) puts obj.one # 以下は実行結果 # 1 # 2 # 3 # 11 # 111
ハッシュを上書き可能にしてあるので、条件によって扱いたいアクセサーを変えたい場合に便利かもしれない*1。またコンストラクタに手を入れたくないときは、このように適当に名付けたアクセサー生成メソッドを作ったほうがよい。
既にアクセサーが登録してあれば、defined?などを使って例外を出すなどの処理を加えてもよいだろう。@attrの初期化位置/タイミングはそこでいいのか後日調べることにする。
*1:そういうときは異なるクラスのオブジェクトを生成したほうがよいかも?