【问题标题】:How do I use hash keys as methods on a class?如何使用哈希键作为类的方法?
【发布时间】:2010-02-10 21:37:12
【问题描述】:

我有一个类和一个哈希。如何让散列的成员以键为方法名动态成为类上的方法?

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

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

u = User.new
puts u.sn

【问题讨论】:

  • 请务必查看 OpenStruct(标准库中的 struct.rb)。它与您所要求的有点不同:它允许 OpenStruct 上的任何方法调用成为访问器,无论它是否已经定义。但它是您不必编写的代码,这有时是一个加分项。

标签: ruby


【解决方案1】:

只需使用 OpenStruct:

require 'ostruct'
class User < OpenStruct
end

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

【讨论】:

    【解决方案2】:
    def method_missing(name, *args, &blk)
      if args.empty? && blk.nil? && @attributes.has_key?(name)
        @attributes[name]
      else
        super
      end
    end
    

    解释:如果你调用一个不存在的方法,method_missing 会被调用,方法名作为第一个参数,然后是给方法的参数,如果给定了,则块。

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

    【讨论】:

    • 我必须在两个地方添加以将其更改为:name.to_s,但它让我到达了我想要的地方!谢谢:)
    • 哦,对了,我没注意到您使用字符串作为哈希键。
    • 此外,您还应该覆盖 responds_to? 以匹配,因为您的班级现在也“响应”该特定消息。
    【解决方案3】:

    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"
    

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

    【讨论】:

      【解决方案4】:

      其实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
      

      【讨论】:

      • 您的解释和扩展示例真的很棒。谢谢!
      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2011-02-05
        • 2011-07-01
        • 2018-02-19
        • 2010-11-07
        • 1970-01-01
        • 2016-02-23
        • 1970-01-01
        • 2011-12-03
        • 2012-06-03
        相关资源
        最近更新 更多