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

问题描述 投票:0回答:5

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

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)
ruby-on-rails ruby hash send
5个回答
4
投票

出于好奇,回答哈希值:

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 } } }

2
投票

Ruby 中的哈希默认情况下不会为您提供这些点方法。

您可以链式发送调用(这适用于任何对象,但通常无法以这种方式访问哈希键):

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

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

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

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

您可以将

: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
调用并不是我经常使用的东西。如果它可以帮助您编写代码,那就太好了。但您不应该发送用户生成的输入,因为它不安全。


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 方法,但这不是最佳实践,甚至可能会产生安全问题。


0
投票

如果您想允许初始化丢失的嵌套键,我建议对 Aleksei Matiushkin' 解决方案进行以下重构

我不想对实际答案进行更改,因为它本身是完全有效的,并且这引入了额外的内容。

hash = { a: {} } # missing some nested keys
def deep_set(hash, value, *keys)
  keys[0...-1].inject(hash) do |acc, h|
    acc[h] ||= {} # initialize the missing keys (ex: b in this case) 
    acc.public_send(:[], h)
  end.public_send(:[]=, keys.last, value)
end

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

0
投票

假设已知密钥存在,那么

Hash#dig
给出了更清晰的解决方案:

hash = { a: { b: { c: 1 } } }

def deep_set(hash, value, *keys)
  hash.dig(*keys[0..-2])[keys[-1]] = value
end

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

hash
#⇒ { a: { b: { c: 42 } } }

这只是示例代码。如果密钥未知或者

deep_set
收到的密钥少于两个,则它将不起作用。这两个问题都是可以解决的,但超出了OP的问题。

© www.soinside.com 2019 - 2024. All rights reserved.