4

この Expando クラスを記述するより良い方法はありますか? 書き方がうまくいかない。Ruby 1.8.7 を使用しています

https://gist.github.com/300462/3fdf51800768f2c7089a53726384350c890bc7c3から引用された開始コード

class Expando
    def method_missing(method_id, *arguments)
        if match = method_id.id2name.match(/(\w*)(\s*)(=)(\s*)(\.*)/)
              puts match[1].to_sym # think this was supposed to be commented 
              self.class.class_eval{ attr_accessor match[1].to_sym } 
              instance_variable_set("#{match[1]}", match[5])
        else
              super.method_missing(method_id, *arguments)
        end  
    end    
end

person = Expando.new 
person.name = "Michael"
person.surname = "Erasmus"
person.age = 29 
4

2 に答える 2

9

The easiest way to write it is to not write it at all! :) See the OpenStruct class, included in the standard library:

require 'ostruct'

record = OpenStruct.new
record.name    = "John Smith"
record.age     = 70
record.pension = 300

If I was going to write it, though, I'd do it like this:

# Access properties via methods or Hash notation
class Expando
  def initialize
    @properties = {}
  end
  def method_missing( name, *args )
    name = name.to_s
    if name[-1] == ?=
      @properties[name[0..-2]] = args.first
    else
      @properties[name]
    end
  end
  def []( key )
    @properties[key]
  end
  def []=( key,val )
    @properties[key] = val
  end
end

person = Expando.new
person.name = "Michael"
person['surname'] = "Erasmus"
puts "#{person['name']} #{person.surname}"
#=> Michael Erasmus
于 2011-01-16T03:40:17.150 に答える
2

使用するための作業を取得しようとしているだけの場合は、代わりExpandoに使用してください。OpenStructしかし、教育的価値のためにこれを行っているのであれば、バグを修正しましょう。

への引数method_missing

これを呼び出すとperson.name = "Michael"への呼び出しに変換されるperson.method_missing(:name=, "Michael")ため、正規表現でパラメータを引き出す必要はありません。割り当てる値は別のパラメーターです。したがって、

if method_id.to_s[-1,1] == "="     #the last character, as a string
   name=method_id.to_s[0...-1]     #everything except the last character
                                   #as a string
   #We'll come back to that class_eval line in a minute
   #We'll come back to the instance_variable_set line in a minute as well.
else
   super.method_missing(method_id, *arguments)
end

instance_variable_set

インスタンス変数名はすべて文字で始まります@。これは単なる構文糖衣ではなく、実際には名前の一部です。したがって、次の行を使用してインスタンス変数を設定する必要があります。

instance_variable_set("@#{name}", arguments[0])

(割り当てている値をarguments配列から取得する方法にも注意してください)

class_eval

self.classExpandoクラス全体を指します。を定義するattr_accessorと、すべての expando がその属性のアクセサーを持ちます。私はそれがあなたが望むものだとは思わない。

むしろ、ブロック内で行う必要がありますclass << self(これは のシングルトン クラスまたは固有クラスですself)。これは の固有クラス内で動作しselfます。

だから私たちは実行します

class << self; attr_accessor name.to_sym ; end

ただし、変数nameは実際には内部でアクセスできないため、最初にシングルトン クラスを選択してから を実行する必要がありますclass_eval。これを行う一般的な方法は、これを独自のメソッドで出力eigenclassすることです。

  def eigenclass
    class << self; self; end
  end

代わりに呼び出しself.eigenclass.class_eval { attr_accessor name.to_sym }ます)

ソリューション

これらすべてを組み合わせると、最終的な解決策は次のようになります

class Expando
  def eigenclass
    class << self; self; end
  end

  def method_missing(method_id, *arguments)
    if method_id.to_s[-1,1] == "=" 
      name=method_id.to_s[0...-1]
      eigenclass.class_eval{ attr_accessor name.to_sym }
      instance_variable_set("@#{name}", arguments[0])
    else
      super.method_missing(method_id, *arguments)
    end      
  end    
end
于 2011-01-16T04:02:32.747 に答える