有没有一种轻量级的方法来锁定哈希上的一组键?
Is there a lightweight way to lock down a set of keys on a Hash?
需要说明的是,我非常乐意自己将此功能作为自定义 class 实现,但我想确保我没有忽略 ruby 或 rails 魔法。我用谷歌搜索了关键字 "ruby rails hash keys values immutable lock freeze" 的每一个有意义的排列。但到目前为止运气不好!
问题:我需要给一个Hash
一组key,可能在运行的时候,然后在不加锁的情况下锁定这组key他们的价值观。类似于以下内容:
to_lock = {}
to_lock[:name] = "Bill"
to_lock[:age] = 42
to_lock.freeze_keys # <-- this is what I'm after, so that:
to_lock[:name] = "Bob" # <-- this works fine,
to_lock[:height] # <-- this returns nil, and
to_lock[:height] = 175 # <-- this throws some RuntimeError
问题:是否有一些 ruby 或 rails 工具允许这样做?
我知道 Object#freeze
and of Immutable::Hash
,但是锁定键 和 值。
坚持开箱即用 ruby,可以通过在 运行 时间操纵 classes 的方法或访问器来满足用例的大部分需求,如this or this, then overriding #method_missing
。但这感觉有点笨拙。这些技术也不是真正的 "lock" 方法或访问器集,添加更多只是很尴尬。到那时,最好简单地编写一个 class 来完全实现上面的代码片段并根据需要进行维护。
您可以通过为您的 "to-lock" 哈希实例定义自定义 []=
来实现此目的,在 添加允许的键之后:
x = { name: nil, age: nil }
def x.[]=(key, value)
# blow up unless the key already exists in the hash
raise 'no' unless keys.include?(key)
super
end
x[:name] # nil
x[:name] = "Bob" # "Bob"
x[:size] # nil
x[:size] = "large" # raise no
请注意,这不会阻止您使用 merge!
.
之类的内容无意中添加密钥
@meagar 提供了一个有趣的解决方案,但指出它仅在尝试使用 Hash#[]
添加键值对时有效。此外,它不会阻止密钥被删除。
这是另一种方法,但它相当笨拙,所以我认为您可能应该寻找一种不同的方法来 skin your cat。
class Hash
def frozen_keys_create
self.merge(self) { |*_,v| [v] }.freeze
end
def frozen_keys_get_value(k)
self[k].first
end
def frozen_keys_put_value(k, new_value)
self[k].replace [new_value]
self
end
def frozen_keys_to_unfrozen
self.merge(self) { |*_,v| v.first }
end
end
现在让我们开始使用它们。
创建一个冻结散列,每个值都封装在一个数组中
sounds = { :cat=>"meow", :dog=>"woof" }.frozen_keys_create
#=> {:cat=>["meow"], :dog=>["woof"]}
sounds.frozen?
#=> true
这会阻止添加密钥:
sounds[:pig] = "oink"
#=> RuntimeError: can't modify frozen Hash
sounds.update(:pig=>"oink")
#=> RuntimeError: can't modify frozen Hash
或删除:
sounds.delete(:cat)
#=> RuntimeError: can't modify frozen Hash
sounds.reject! { |k,_| k==:cat }
#=> RuntimeError: can't modify frozen Hash
获取一个值
sounds.frozen_keys_get_value(:cat)
#=> "meow"
更改值
sounds.frozen_keys_put_value(:dog, "oooooowwwww")
#=> {:cat=>["meow"], :dog=>["oooooowwwww"]}
转换为键未冻结的散列
new_sounds = sounds.frozen_keys_to_unfrozen
#=> {:cat=>"meow", :dog=>"oooooowwwww"}
new_sounds.frozen?
#=> false
添加和删除键
甚至可以添加(私有的,也许)方法来添加或删除键以覆盖所需的行为。
class Hash
def frozen_keys_add_key_value(k, value)
frozen_keys_to_unfrozen.tap { |h| h[k] = value }.frozen_keys_create
end
def frozen_keys_delete_key(k)
frozen_keys_to_unfrozen.reject! { |key| key == k }.frozen_keys_create
end
end
sounds = { :cat=>"meow", :dog=>"woof" }.frozen_keys_create
#=> {:cat=>["meow"], :dog=>["oooowwww"]}
new_sounds = sounds.frozen_keys_add_key_value(:pig, "oink")
#=> {:cat=>["meow"], :dog=>["woof"], :pig=>["oink"]}
new_sounds.frozen?
#=> true
newer_yet = new_sounds.frozen_keys_delete_key(:cat)
#=> {:dog=>["woof"], :pig=>["oink"]}
newer_yet.frozen?
#=> true
听起来像是内置 Struct
的一个很好的用例
irb(main):001:0> s = Struct.new(:name, :age).new('Bill', 175)
=> #<struct name="Bill", age=175>
irb(main):002:0> s.name = 'Bob'
=> "Bob"
irb(main):003:0> s.something_else
NoMethodError: undefined method `something_else' for #<struct name="Bob", age=175>
from (irb):3
from /home/jtzero/.rbenv/versions/2.3.0/bin/irb:11:in `<main>'
需要说明的是,我非常乐意自己将此功能作为自定义 class 实现,但我想确保我没有忽略 ruby 或 rails 魔法。我用谷歌搜索了关键字 "ruby rails hash keys values immutable lock freeze" 的每一个有意义的排列。但到目前为止运气不好!
问题:我需要给一个Hash
一组key,可能在运行的时候,然后在不加锁的情况下锁定这组key他们的价值观。类似于以下内容:
to_lock = {}
to_lock[:name] = "Bill"
to_lock[:age] = 42
to_lock.freeze_keys # <-- this is what I'm after, so that:
to_lock[:name] = "Bob" # <-- this works fine,
to_lock[:height] # <-- this returns nil, and
to_lock[:height] = 175 # <-- this throws some RuntimeError
问题:是否有一些 ruby 或 rails 工具允许这样做?
我知道 Object#freeze
and of Immutable::Hash
,但是锁定键 和 值。
坚持开箱即用 ruby,可以通过在 运行 时间操纵 classes 的方法或访问器来满足用例的大部分需求,如this or this, then overriding #method_missing
。但这感觉有点笨拙。这些技术也不是真正的 "lock" 方法或访问器集,添加更多只是很尴尬。到那时,最好简单地编写一个 class 来完全实现上面的代码片段并根据需要进行维护。
您可以通过为您的 "to-lock" 哈希实例定义自定义 []=
来实现此目的,在 添加允许的键之后:
x = { name: nil, age: nil }
def x.[]=(key, value)
# blow up unless the key already exists in the hash
raise 'no' unless keys.include?(key)
super
end
x[:name] # nil
x[:name] = "Bob" # "Bob"
x[:size] # nil
x[:size] = "large" # raise no
请注意,这不会阻止您使用 merge!
.
@meagar 提供了一个有趣的解决方案,但指出它仅在尝试使用 Hash#[]
添加键值对时有效。此外,它不会阻止密钥被删除。
这是另一种方法,但它相当笨拙,所以我认为您可能应该寻找一种不同的方法来 skin your cat。
class Hash
def frozen_keys_create
self.merge(self) { |*_,v| [v] }.freeze
end
def frozen_keys_get_value(k)
self[k].first
end
def frozen_keys_put_value(k, new_value)
self[k].replace [new_value]
self
end
def frozen_keys_to_unfrozen
self.merge(self) { |*_,v| v.first }
end
end
现在让我们开始使用它们。
创建一个冻结散列,每个值都封装在一个数组中
sounds = { :cat=>"meow", :dog=>"woof" }.frozen_keys_create
#=> {:cat=>["meow"], :dog=>["woof"]}
sounds.frozen?
#=> true
这会阻止添加密钥:
sounds[:pig] = "oink"
#=> RuntimeError: can't modify frozen Hash
sounds.update(:pig=>"oink")
#=> RuntimeError: can't modify frozen Hash
或删除:
sounds.delete(:cat)
#=> RuntimeError: can't modify frozen Hash
sounds.reject! { |k,_| k==:cat }
#=> RuntimeError: can't modify frozen Hash
获取一个值
sounds.frozen_keys_get_value(:cat)
#=> "meow"
更改值
sounds.frozen_keys_put_value(:dog, "oooooowwwww")
#=> {:cat=>["meow"], :dog=>["oooooowwwww"]}
转换为键未冻结的散列
new_sounds = sounds.frozen_keys_to_unfrozen
#=> {:cat=>"meow", :dog=>"oooooowwwww"}
new_sounds.frozen?
#=> false
添加和删除键
甚至可以添加(私有的,也许)方法来添加或删除键以覆盖所需的行为。
class Hash
def frozen_keys_add_key_value(k, value)
frozen_keys_to_unfrozen.tap { |h| h[k] = value }.frozen_keys_create
end
def frozen_keys_delete_key(k)
frozen_keys_to_unfrozen.reject! { |key| key == k }.frozen_keys_create
end
end
sounds = { :cat=>"meow", :dog=>"woof" }.frozen_keys_create
#=> {:cat=>["meow"], :dog=>["oooowwww"]}
new_sounds = sounds.frozen_keys_add_key_value(:pig, "oink")
#=> {:cat=>["meow"], :dog=>["woof"], :pig=>["oink"]}
new_sounds.frozen?
#=> true
newer_yet = new_sounds.frozen_keys_delete_key(:cat)
#=> {:dog=>["woof"], :pig=>["oink"]}
newer_yet.frozen?
#=> true
听起来像是内置 Struct
的一个很好的用例irb(main):001:0> s = Struct.new(:name, :age).new('Bill', 175)
=> #<struct name="Bill", age=175>
irb(main):002:0> s.name = 'Bob'
=> "Bob"
irb(main):003:0> s.something_else
NoMethodError: undefined method `something_else' for #<struct name="Bob", age=175>
from (irb):3
from /home/jtzero/.rbenv/versions/2.3.0/bin/irb:11:in `<main>'