在 Ruby 中创建 Expando 对象

发布于 2024-10-12 09:10:34 字数 810 浏览 8 评论 0原文

有没有更好的方法来编写这个 Expando 类?按照这样的写法是行不通的。 我正在使用 Ruby 1.8.7

起始代码,引用自 https://gist.github.com/300462/3fdf51800768f2c7089a537 26384350c890bc7c3

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 

Is there a better way to write this Expando class? The way it is written does not work.
I'm using Ruby 1.8.7

starting code quoted from 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 

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

灼疼热情 2024-10-19 09:10:35

最简单的写法就是根本不写! :) 请参阅标准库中包含的 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

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
相守太难 2024-10-19 09:10:35

如果您只是想获得一个可用的 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

If you're just trying to get a working Expando for use, use OpenStruct instead. But if you're doing this for educational value, let's fix the bugs.

The arguments to method_missing

When you call person.name = "Michael" this is translated into a call to person.method_missing(:name=, "Michael"), so you don't need to pull the parameter out with a regular expression. The value you're assigning is a separate parameter. Hence,

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 names all start with the @ character. It's not just syntactic sugar, it's actually part of the name. So you need to use the following line to set the instance variable:

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

(Notice also how we pulled the value we're assigning out of the arguments array)

class_eval

self.class refers to the Expando class as a whole. If you define an attr_accessor on it, then every expando will have an accessor for that attribute. I don't think that's what you want.

Rather, you need to do it inside a class << self block (this is the singleton class or eigenclass of self). This operates inside the eigenclass for self.

So we would execute

class << self; attr_accessor name.to_sym ; end

However, the variable name isn't actually accessible inside there, so we're going to need to single out the singleton class first, then run class_eval. A common way to do this is to out this with its own method eigenclass So we define

  def eigenclass
    class << self; self; end
  end

and then call self.eigenclass.class_eval { attr_accessor name.to_sym } instead)

The solution

Combine all this, and the final solution works out to

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文