如何在 Ruby 哈希中动态设置嵌套键的值
How to set dynamically value of nested key in Ruby hash
应该很容易,但我找不到合适的解决方案。
对于第一级键:
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 中的散列默认情况下不会为您提供这些点方法。
您可以链式发送调用(这适用于任何对象,但您不能以这种方式正常访问散列键):
"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
赋值给变量,使其在某种意义上成为 'dynamic'。
有更高级的方法可以做到这一点,例如 dig
在较新的 Ruby
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
那么你可以给你的散列 dot-method 访问器。老实说,链接 send
调用并不是我经常使用的东西。如果它能帮助你编写代码,那就太好了。但是你不应该发送 user-generated 输入,因为它不安全。
哈希答案,出于好奇:
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 } } }
虽然您可以实现一些方法来按照您现在设置的方式来做事,但我强烈建议您重新考虑您的数据结构。
为了澄清一些术语,示例中的 key
不是键,而是方法调用。在 Ruby 中,当你有像 my_thing.my_other_thing
这样的代码时,my_other_thing 总是一个方法,而不是一个键,至少在这个术语的正确意义上是这样。
的确,您可以通过以这种方式链接对象来创建 hash-like 结构,但这确实有代码的味道。如果您将 foo.bar.lolo
视为一种在散列中查找嵌套 lolo
键的方法,那么您可能应该使用常规散列。
x = {foo: {bar: 'lolo'}}
x[:foo][:bar] # => 'lolo'
x[:foo][:bar] = 'new_value' # => 'new_value'
另外,虽然 send/instance_eval 方法可以这样使用,但这不是最佳实践,甚至会产生安全问题。
应该很容易,但我找不到合适的解决方案。 对于第一级键:
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 中的散列默认情况下不会为您提供这些点方法。
您可以链式发送调用(这适用于任何对象,但您不能以这种方式正常访问散列键):
"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
赋值给变量,使其在某种意义上成为 'dynamic'。
有更高级的方法可以做到这一点,例如 dig
在较新的 Ruby
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
那么你可以给你的散列 dot-method 访问器。老实说,链接 send
调用并不是我经常使用的东西。如果它能帮助你编写代码,那就太好了。但是你不应该发送 user-generated 输入,因为它不安全。
哈希答案,出于好奇:
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 } } }
虽然您可以实现一些方法来按照您现在设置的方式来做事,但我强烈建议您重新考虑您的数据结构。
为了澄清一些术语,示例中的 key
不是键,而是方法调用。在 Ruby 中,当你有像 my_thing.my_other_thing
这样的代码时,my_other_thing 总是一个方法,而不是一个键,至少在这个术语的正确意义上是这样。
的确,您可以通过以这种方式链接对象来创建 hash-like 结构,但这确实有代码的味道。如果您将 foo.bar.lolo
视为一种在散列中查找嵌套 lolo
键的方法,那么您可能应该使用常规散列。
x = {foo: {bar: 'lolo'}}
x[:foo][:bar] # => 'lolo'
x[:foo][:bar] = 'new_value' # => 'new_value'
另外,虽然 send/instance_eval 方法可以这样使用,但这不是最佳实践,甚至会产生安全问题。