如何在类上使用哈希键作为方法?

我有一个类和一个哈希。 如何使用键作为方法名称,使哈希的成员动态地成为类的方法?

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

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

 u = User.new puts u.sn 

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

说明:如果调用一个不存在的方法,则调用method_missing,并将方法名称作为第一个参数,然后是给定方法的参数和块(如果给出了一个)。

在上面我们说如果一个没有定义的方法被调用而没有参数并且没有一个块,并且哈希有一个方法名为key的条目,它将返回该条目的值。 否则它将照常进行。

只需使用OpenStruct:

 require 'ostruct' class User < OpenStruct end u = User.new :sn => 222 u.sn 

sepp2k的解决方案是可行的方法。 但是,如果您的@attributes在初始化后永远不会改变并且您需要速度,那么您可以这样做:

 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" 

这会提前生成所有方法……

实际上, 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 

你也可以用Struct来做到这一点:

 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,但要小心它不会创建方法,如果它已经在实例方法中有它,你可以通过使用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 

你可以为此“借用”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') 

请注意,u.job是User :: Job - 此类是自动创建的。 分配给嵌套值时有一个问题。 您不能只分配哈希,但必须将其包装在适当的类中:

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

不幸的是,当你想添加一个没有相应类的嵌套哈希时,它更加丑陋,因为你必须强制ARes从哈希创建类:

 # 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