特異クラスを使ってRailsのmodelを動的に作成する
Railsのmodelを動的に定義したくて悩んでたらTwitterで @antimon2 さんがうまい方法を教えてくれました。
# このmodelを継承したクラスを作成すれば # validationを行ったり、viewでform_forが使える class SimpleModel include ActiveModel::Validations include ActiveModel::Conversion extend ActiveModel::Naming def initialize(attributes = {}) attributes.each do |name, value| send("#{name}=", value) end end def persisted? false end end
# controllerの一部 # SimpleModelの特異クラスを定義 # headが1に対してdetailsが多 # detailのforce_inputがtrueなら必須入力とする def create_model_instance(head) model = SimpleModel.new detail_class = class << model;self;end detail_class.class_eval do head.details.each do |d| column = "detail#{d.id.to_s}".to_sym attr_accessor column if d.force_input validates column, :presence => true end end end model end
初め
detail_class = class << model;self;end
の部分がよく分からなくて悩んでたんだけど次のエントリを見て理解できた。
http://d.hatena.ne.jp/unageanu/20080729
SimpleModelの特異クラスを取り出しているらしい。
んでは、一旦detail_classを作成してdetail_class.class_evalするより直接次のように書いたほうが
シンプルじゃね?と試してみたら...
def create_model_instance(head) model = SimpleModel.new class << model head.details.each do |d| column = "detail#{d.id.to_s}".to_sym attr_accessor column if d.force_input validates column, :presence => true end end end model end
ruby-1.9.2-p0 > x = create_model_instance(Head.first) NameError: undefined local variable or method `head' for #<Class:#<SimpleModel:0x00000101f9ef78>>
この位置ではheadは取得できないようです。スコープ範囲外ってことか。なるほどネ...
特異メソッドについては理解してたつもりだったんだけど、今回特異クラスについて学習することができました。
特異クラスを使えばattr_accessorやvalidatesなどのクラスに対して行う操作が特定のインスタンスに関連する
クラスにのみ行うことができます。勉強になりました。