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

写它的最简单方法是根本不写它!:) 请参阅标准库中包含的OpenStruct类:

require 'ostruct'

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

但是,如果我要写它,我会这样做:

# 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"this 时,它会转换为对 的调用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在其上定义 an,那么每个expando 都将具有该属性的访问器。我不认为那是你想要的。

相反,您需要在一个class << self块内进行(这是单例类或 eigenclass self)。这在 的 eigenclass 内部运行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 回答