2010-02-10 58 views
15

我有一个类和一个哈希。我怎样才能让哈希的成员动态地成为类的方法,并且使用方法名作为键?如何使用散列键作为类的方法?

class User 
    def initialize 
    @attributes = {"sn" => "Doe", "givenName" => "John"} 
    end 
end 

例如,我想能够有以下输出Doe

u = User.new 
puts u.sn 
+4

请务必查看OpenStruct(标准库中的struct.rb)。这与你所要求的有点不同:它允许OpenStruct的任何方法调用成为一个访问器,无论它是否已经被定义。但是它不需要编写代码,有时候可以加上代码。 – 2010-02-10 22:22:28

回答

14
def method_missing(name, *args, &blk) 
    if args.empty? && blk.nil? && @attributes.has_key?(name) 
    @attributes[name] 
    else 
    super 
    end 
end 

解释:如果你调用一个方法,它不存在,method_missing方法被调用,方法的名称作为第一个参数,然后是给定的方法和块的参数(如果给出的话)。

在上面我们说如果一个没有定义的方法在没有参数的情况下被调用,并且没有一个块并且这个散列有一个方法名作为关键字的条目,它将返回该条目的值。否则,它将照常进行。

+0

我必须添加它才能将它更改为:name.to_s在这两个地方,但它让我在我想要的地方!谢谢:) – Michael 2010-02-10 22:10:36

+0

哦,对,我没有注意到你使用字符串作为散列键。 – sepp2k 2010-02-10 22:12:29

+0

此外,您还应该重写'responds_to?'以匹配,因为您的课程现在也“响应”该特定消息。 – 2012-01-17 15:44:20

4

sepp2k的解决方案是要走的路。但是,如果你的@属性初始化后,永远不会改变,你需要速度,那么你可以做这样:

class User 
    def initialize 
    @attributes = {"sn" => "Doe", "givenName" => "John"} 
    @attributes.each do |k,v| 
     self.class.send :define_method, k do v end 
    end 
    end 
end 

User.new.givenName # => "John" 

这会产生提前的所有方法...

3

其实severin有更好的主意,只是因为method_missing的使用是一个不好的做法,并非所有的时间,但大部分时间。

severin提供的代码存在一个问题:它返回已传递给初始值设定项的值,因此无法对其进行更改。我建议你一点点不同的方法:

class User < Hash 
    def initialize(attrs) 
    attrs.each do |k, v| 
     self[k] = v 
    end 
    end 

    def []=(k, v) 
    unless respond_to?(k) 
     self.class.send :define_method, k do 
     self[k] 
     end 
    end 

    super 
    end 
end 

让我们检查一下:

u = User.new(:name => 'John') 
p u.name 
u[:name] = 'Maria' 
p u.name 

,你也可以用结构做到这一点:

attrs = {:name => 'John', :age => 22, :position => 'developer'} 
keys = attrs.keys 

user = Struct.new(*keys).new(*keys.map { |k| attrs[k] }) 

让我们测试一下:

p user 
p user.name 
user[:name] = 'Maria' 
p user.name 
user.name = 'Vlad' 
p user[:name] 

甚至是OpenStruct,但是是ca reful它不会产生方法,如果它已经有它在实例方法,您可以通过使用OpenStruct.instance_methods寻找那些(因为类型的使用,我现在使用第二种方法):

attrs = {:name => 'John', :age => 22, :position => 'developer'} 
user = OpenStruct.new(attrs) 

是的,这样容易:

user.name 
user[:name] # will give you an error, because OpenStruct isn't a Enumerable or Hash 
+0

你的解释和扩展例子真的很棒。谢谢! – 2013-03-07 16:55:43

29

只需使用OpenStruct:

require 'ostruct' 
class User < OpenStruct 
end 

u = User.new :sn => 222 
u.sn 
1

您可以 “借用” 的ActiveResource这一点。它甚至处理嵌套哈希和分配:

require 'active_resource' 
class User < ActiveResource::Base 
    self.site = '' # must be a string 
end 

用法:

u = User.new "sn" => "Doe", "givenName" => "John", 'job'=>{'description'=>'Engineer'} 
u.sn # => "Doe" 
u.sn = 'Deere' 
u.job.description # => "Engineer" 
# deletion 
u.attributes.delete('givenName') 

注意,美。job是一个User :: Job - 这个类是自动创建的。 分配给嵌套值时有一个问题。你不能只分配一个哈希,但必须在适当的类包起来:

u.job = User::Job.new 'foo' => 'bar' 
u.job.foo # => 'bar 

不幸的是,当你想添加不具有对应的级嵌套哈希,这是丑陋的,因为你必须强制AR从哈希创建类:

# assign the hash first 
u.car = {'make' => 'Ford'} 
# force refresh - this can be put into a method 
u = User.new Hash.from_xml(u.to_xml).values.first