stringオブジェクトがクラス名となるクラスを生成する
FacebookやTwitterで異なる写真のURLを一元的に取り扱うために、PhotoUrlクラスを定義して、それをFacebookPhotoUrlやTwitterPhotoUrlに継承するようにしたのはいいけど、結局、ケースごとにインスタンス化するクラスを分けるコードが残って、なんか違うだろうという感じだった。
def photo_tag(user, options = {:size => :normal}) case user.provider when "twitter" photo_url = TwitterPhotoUrl.new(user, options) when "facebook" photo_url = FacebookPhotoUrl.new(user, options) else photo_url = LocalPhotoUrl.new(user, options) end : : end
で、これを解消するために、クラスの命名規則を決めうちにして、userモデルのproviderフィールドの文字列からクラス名を作ってインスタンス化するようにしてみた。
class PhotoUrl def PhotoUrl.generate(user, options) # You need to name classes like so: ProviderPhotoUrl provider = user.provider klass = if provider provider.capitalize + "PhotoUrl" else "LocalPhotoUrl" end Kernel.const_get(klass).new(user, options) end def initialize(user, options) @user, @options = user, options end : :
klassは文字列オブジェクト。これをそのままklass.newとは当然できない。Classクラスのオブジェクトである各種クラスのオブジェクトは、トップレベルの定数として参照を持っているので、Kernel#const_get(klass).new とすればよい。
これでクライアント側のコードは、
def photo_tag(user, options = {:size => :normal}) photo = PhotoUrl.generate(user, options) : : end
と、1行になった。