【问题标题】:hash['key'] to hash.key in Rubyhash['key'] 到 Ruby 中的 hash.key
【发布时间】:2009-11-18 02:51:03
【问题描述】:

我有一个哈希

foo = {'bar'=>'baz'}

我想打电话给foo.bar #=> 'baz'

我的动机是将 activerecord 查询重写为原始 sql 查询(使用 Model#find_by_sql)。这将返回一个以 SELECT 子句值作为键的散列。但是,我现有的代码依赖于 object.method 点表示法。我想做最少的代码重写。谢谢。

编辑:Lua 似乎有这个功能:

point = { x = 10, y = 20 }   -- Create new table
print(point["x"])            -- Prints 10
print(point.x)               -- Has exactly the same meaning as line above

【问题讨论】:

  • 为什么 Lua 有这个“特性”很重要? JavaScript 也是如此。这与这个问题并没有特别密切的关系。
  • 我只是想更好地说明我想用 Ruby 完成什么。

标签: ruby activerecord hash


【解决方案1】:

为此有一些宝石。这是我最近的 gem,hash_dot,以及我在 RubyGems 上发布时发现的其他一些名称相似的 gem,包括 dot_hash

HashDot 允许点符号语法,同时仍然解决了@avdi 解决的 NoMethodErrors 问题。它比使用 OpenStruct 创建的对象更快、更易遍历。

require 'hash_dot'
a = {b: {c: {d: 1}}}.to_dot
a.b.c.d => 1

require 'open_struct'
os = OpenStruct.new(a)
os.b => {c: {d: 1}}
os.b.c.d => NoMethodError

当调用非方法时,它也保持预期的行为。

a.non_method => NoMethodError

请随时向 HashDot 提交改进或错误。

【讨论】:

    【解决方案2】:
    >> require 'ostruct'
    => []
    >> foo = {'bar'=>'baz'}
    => {"bar"=>"baz"}
    >> foo_obj = OpenStruct.new foo
    => #<OpenStruct bar="baz">
    >> foo_obj.bar
    => "baz"
    >>
    

    【讨论】:

    • 不鼓励使用 OpenStruct,因为每次实例化都会使 Ruby 方法缓存失效,这会减慢整个应用程序的速度。
    • @ChrisHeald:想详细说明一下这个命题吗?谁不鼓励使用 OpenStruct?它是否使 整个 方法缓存或符号的方法缓存无效?
    • @Steen 如果你在 google 上搜索“ruby 方法缓存”,前几个命中将是 Charlie Somerville 和已故的 James Golick 对它的解释。在 Ruby 2.1 之前,实例化一个 ostruct 方法会转储 整个 方法缓存。在 2.1+ 中,它转储所有 ostruct 实例及其子类实例的方法缓存。
    • 这不是递归的!首选@gabor解决方案
    【解决方案3】:

    一个很好的解决方案:

    class Hash
      def method_missing(method, *opts)
        m = method.to_s
        if self.has_key?(m)
          return self[m]
        elsif self.has_key?(m.to_sym)
          return self[m.to_sym]
        end
        super
      end
    end
    

    注意:这个实现只有一个已知的错误:

    x = { 'test' => 'aValue', :test => 'bar'}
    x.test # => 'aValue'
    

    如果你更喜欢符号查找而不是字符串查找,那么交换两个 'if' 条件

    【讨论】:

    • 可能没有解决他的问题,但这解决了我的一个问题。找了好久才找到这个,谢谢!
    • 我一直在这样做 self[m] || self[m.to_s] || super 而不是 if/else
    • 仅当哈希键与现有方法名称不匹配时才有效(例如 Hash#zip)。
    • @JoeVanDyk 同样的限制也适用于 OpenStruct。如果您在用于初始化 OpenStruct 对象的哈希中有一个键值对,并且该键与 OpenStruct 的现有方法具有相同的名称,则它更喜欢方法调用而不是返回哈希值。我看到的唯一区别是操作系统是标准的,可能快一点,并且有一些其他的对象管理实用程序。使用 method_missing 方式同样强大,您可以轻松扩展现有代码/库。
    • OpenStruct 真的很慢。 stackoverflow.com/questions/1177594/ruby-struct-vs-openstruct/… 加上它会清除方法缓存。
    【解决方案4】:

    您要查找的内容称为OpenStruct。它是标准库的一部分。

    【讨论】:

      【解决方案5】:

      您可以向 Hash 添加一些行为来进行查找,而不是从哈希中复制所有内容。

      如果添加此定义,则扩展 Hash 以将所有未知方法作为哈希查找处理:

      class Hash
        def method_missing(n)
          self[n.to_s]
        end
      end
      

      请记住,这意味着如果您在哈希上调用错误的方法,您将永远不会看到错误 - 您只会得到相应的哈希查找将返回的任何内容。

      您可以通过仅将方法放入特定哈希 - 或您需要的任意数量的哈希来大大减少可能导致的调试问题:

      a={'foo'=>5, 'goo'=>6}
      def a.method_missing(n)
         self[n.to_s]
      end
      

      另一个观察结果是,当method_missing 被系统调用时,它会给你一个Symbol 参数。我的代码将其转换为String。如果您的哈希键不是字符串,则此代码将永远不会返回这些值 - 如果您按符号而不是字符串作为键,只需将上面的 n 替换为 n.to_s

      【讨论】:

      • 在我参与的一个大型项目中,有人在早期就这样做了。它引起了各种微妙的错误,因为对系统中任何散列的错误方法调用只会返回 nil 而不是用 NoMethodError 警告我们。从系统中删除需要很长时间,因为整个系统的代码都依赖于它。这是我见过的最灾难性的代码类扩展之一。
      • 要(大部分)解决这个问题,你可以这样做if has_key? n.to_s then self[n.to_s] else raise NoMethodError
      • Avdi - 我当然同意滥用/过度使用元编程是一个问题。可能有更好的方法来解决整个问题(可能是 OpenStruct),但我只是把它当作练习。
      猜你喜欢
      • 2012-05-12
      • 2020-11-28
      • 1970-01-01
      • 2019-12-20
      • 1970-01-01
      • 2018-06-27
      • 1970-01-01
      • 1970-01-01
      • 2015-10-12
      相关资源
      最近更新 更多