在Ruby中创建一个Expando对象

时间:2011-01-16 03:31:29

标签: ruby expando

有没有更好的方法来编写此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 

2 个答案:

答案 0 :(得分: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

答案 1 :(得分: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.class指整个Expando类。如果您在其上定义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