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

时间:2010-02-10 21:37:12

标签: ruby

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

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

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

u = User.new
puts u.sn

5 个答案:

答案 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