我有一个类和一个哈希。如何使用键作为方法名称来动态地成为类的方法?
class User
def initialize
@attributes = {"sn" => "Doe", "givenName" => "John"}
end
end
例如,我希望能够得到以下输出Doe
:
u = User.new
puts u.sn
答案 0 :(得分:30)
只需使用OpenStruct:
require 'ostruct'
class User < OpenStruct
end
u = User.new :sn => 222
u.sn
答案 1 :(得分:14)
def method_missing(name, *args, &blk)
if args.empty? && blk.nil? && @attributes.has_key?(name)
@attributes[name]
else
super
end
end
说明:如果调用一个不存在的方法,则调用method_missing,并将方法的名称作为第一个参数,然后是给定方法的参数和块(如果有的话)。
在上面我们说如果一个没有定义的方法被调用而没有参数且没有一个块,并且哈希有一个方法名为key的条目,它将返回该条目的值。否则它将照常进行。
答案 2 :(得分:4)
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"
这会提前生成所有方法......
答案 3 :(得分: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
你也可以用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
答案 4 :(得分:2)
您可以为此“借用”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