Ruby - 在运行时动态地向类中添加属性

11

我正在寻找一种在运行时向已定义的类添加属性的方法,或者更好的方式:

class Client
    attr_accessor :login, :password
    def initialize args = {}
        self.login    = args[:login]
        self.password = args[:password]
    end
end

但是,我有这个哈希值

{:swift_bic=>"XXXX", :account_name=>"XXXX", :id=>"123", :iban=>"XXXX"} 

我希望这个哈希值成为我的客户端实例的一部分,像这样:

client = Client.new :login => 'user', :password => 'xxxxx'

然后以神奇的魔力

client @@%$%PLIM!!! {:swift_bic=>"XXXX", :account_name=>"XXXX", :id=>"123", :iban=>"XXXX"} 

我将能够访问

client.swift_bic => 'XXXX'
client.account_name => 'XXXX'
client.id => 123

而且我也想保持适当的对象结构,例如:

Client.new(:login => 'user', :password => 'xxxxx').inspect
#<Client:0x1033c4818 @password='xxxxx', @login='user'>

魔法之后

client.inspect
#<Client:0x1033c4818 @password='xxxxx', @login='user', @swift_bic='XXXX', @account_name='XXXX' @id => '123', @iban => 'XXXX'>

有没有可能让我得到一个非常漂亮和格式良好的json呢?

这真的可能吗?

我从一个Web服务中获取此哈希值,因此如果其中有新属性,我就不知道了,然后每次他们升级服务时我都必须更新我的应用程序。所以,我正在尝试避免这种情况:/

谢谢大家。

:)


2
如果这个类所做的只是存储数据,那么我会使用OpenStruct。它正是为此目的而存在的。http://www.ruby-doc.org/core/classes/OpenStruct.html - thorncp
4个回答

16
< p >采用 method_missing 方法可以解决问题,但是如果你添加访问器后需要经常使用它们,那么最好像下面这样将它们作为真正的方法添加:

class Client
  def add_attrs(attrs)
    attrs.each do |var, value|
      class_eval { attr_accessor var }
      instance_variable_set "@#{var}", value
    end
  end
end

这将使它们像普通实例变量一样工作,但仅限于一个client


1
哈哈哈!!神奇的魔法确实存在,非常感谢mckeed,正是我在寻找的!干杯 - zanona
3
默认情况下对我不起作用(我尝试了1.8.7和1.9.1),我通过在self的单例类上调用class_eval使其工作。 除了第3行变成(class << self ; self ; end).class_eval { attr_accessor var }之外,其他都一样。 另一种编写方式是:(class << self ; self ; end).send :attr_accessor , var - Joshua Cheek
1
好的,没错。按照我写的方式,这确实需要ActiveSupport。 - mckeed

2

我认为最好的解决方案是mckeed的。

但是这里还有另一个值得考虑的想法。如果你想的话,你可以子类化OpenStruct:

require 'ostruct'

class Client < OpenStruct
  def initialize args = {}
    super
  end
  def add_methods( args = Hash.new )
    args.each do |name,initial_value|
      new_ostruct_member name
      send "#{name}=" , initial_value
    end
  end
end

client = Client.new :login => 'user', :password => 'xxxxx'
client.add_methods :swift_bic=>"XXXX", :account_name=>"XXXX", :iban=>"XXXX" , :to_s => 5
client # => #<Client login="user", password="xxxxx", swift_bic="XXXX", account_name="XXXX", iban="XXXX", to_s=5>

client.swift_bic      # => "XXXX"
client.account_name   # => "XXXX"

然而,这种解决方案存在两个问题。OpenStruct使用method_missing方法,因此如果您定义了像id这样的方法,在1.8版本中它会查找object_id而不是查找您的方法。

第二个问题是它使用了一些关于OpenStruct实现方式的私有知识。因此,将来可能会更改,破坏此代码(记录一下,我检查了1.8.7-1.9.2,这是兼容的)。


0
我建议不要使用method_missing,因为它会创建很多'神奇'的功能,很难进行文档化或者在没有阅读method_missing代码的情况下理解。相反,可以考虑使用OpenStruct,就像建议的那样 - 你甚至可以创建一个继承自它的类,例如:
class Client < OpenStruct
  ...
end

你将能够使用收到的哈希值初始化客户端。


0

看看 Object.method_missing。这将在你的对象被调用时调用,如果没有定义方法,则会被调用。你可以定义此函数并使用它来检查未定义的方法是否与哈希值之一的名称匹配。如果匹配,则返回哈希值。

您还可以定义自己的 inspect 函数,并生成包含您想要包含的任何内容的输出字符串。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接