如何在Ruby哈希中动态设置嵌套键的值

时间:2017-01-13 16:37:08

标签: ruby-on-rails ruby hash send

应该很容易,但我找不到合适的解决方案。 对于第一级键:

resource.public_send("#{key}=", value)

foo.bar.lolo

我知道我可以像下面那样得到它:

'foo.bar.lolo'.split('.').inject(resource, :send)

resource.instance_eval("foo.bar.lolo")

但是如何将值设置为最后一个变量,假设我不知道嵌套级别,它可能是第二个或第三个。

是否有一般方法可以为所有级别执行此操作? 对于我的例子,我可以像下面这样做:

resource.public_send("fofo").public_send("bar").public_send("lolo=", value)

3 个答案:

答案 0 :(得分:3)

出于好奇而回答哈希:

hash = { a: { b: { c: 1 } } }
def deep_set(hash, value, *keys)
  keys[0...-1].inject(hash) do |acc, h|
    acc.public_send(:[], h)
  end.public_send(:[]=, keys.last, value)
end

deep_set(hash, 42, :a, :b, :c)
#⇒ 42
hash
#⇒ { a: { b: { c: 42 } } }

答案 1 :(得分:0)

ruby​​中的哈希默认情况下不会给你这些点方法。

你可以链接发送调用(这适用于任何对象,但通常不能以这种方式访问​​哈希键):

  "foo".send(:downcase).send(:upcase)

使用嵌套哈希时,可变性的棘手概念是相关的。例如:

  hash = { a: { b: { c: 1 } } }
  nested = hash[:a][:b]
  nested[:b] = 2
  hash
  # => { a: { b: { c: 2 } }

“Mutability”在这里意味着当您将嵌套哈希存储到单独的变量中时,它实际上仍然是指向原始哈希的指针。可变性对于这种情况很有用,但如果您不理解它也可能会产生错误。

您可以为变量指定:a:b,使其在某种意义上具有“动态”。

有更多高级方法可以做到这一点,例如新版Ruby中的dig

versions.

  hash = { a: { b: { c: 1 } } }
  keys_to_get_nested_hash = [:a, :b]
  nested_hash = hash.dig *keys_to_get_nested_hash
  nested_hash[:c] = 2
  hash
  # => { a: { b: { c: 2 } } }

如果你使用OpenStruct,那么你可以给你的哈希点方法访问器。老实说,链接send电话不是我经常使用的。如果它可以帮助您编写代码,那就太棒了。但是你不应该发送用户生成的输入,因为它是不安全的。

答案 2 :(得分:0)

虽然您可以实施某些方法来按现在的方式设置它们,但我强烈建议您重新考虑数据结构。

为了澄清您的一些术语,示例中的key不是键,而是方法调用。在Ruby中,当你有像my_thing.my_other_thing这样的代码时,my_other_thing总是一种方法,而且永远不是一个密钥,至少不是正确的术语。

你可以通过这种方式链接对象来创建一个类似哈希的结构,但这有一个真正的代码味道。如果您认为foo.bar.lolo是一种在哈希中查找嵌套lolo键的方法,那么您应该使用常规哈希。

x = {foo: {bar: 'lolo'}}
x[:foo][:bar] # => 'lolo'
x[:foo][:bar] = 'new_value' # => 'new_value'

此外,虽然send / instance_eval方法可以这种方式使用,但这不是最佳做法,甚至可能会产生安全问题。