对特定值的参数哈希进行切片
总结
给定一个哈希,基于要使用的键列表创建子集哈希的最有效方法是什么?
h1 = { a:1, b:2, c:3 } # Given a hash...
p foo( h1, :a, :c, :d ) # ...create a method that...
#=> { :a=>1, :c=>3, :d=>nil } # ...returns specified keys...
#=> { :a=>1, :c=>3 } # ...or perhaps only keys that exist
详细信息
Sequel 数据库工具包允许通过传入哈希来创建或更新模型实例
foo = Product.create( hash_of_column_values )
foo.update( another_hash )
: Sinatra Web 框架提供了一个名为 params
的哈希,其中包括表单变量、查询字符串参数和还有路线匹配。
如果我创建一个仅包含与数据库列名称相同的字段的表单并将其发布到此路由,则一切都会非常方便:
post "/create_product" do
new_product = Product.create params
redirect "/product/#{new_product.id}"
end
但是,这既脆弱又危险。这是危险的,因为恶意黑客可能会发布包含无意更改的列的表单并更新它们。它很脆弱,因为在这条路线上使用相同的形式是行不通的:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
product.update(params)
#=> <Sequel::Error: method foo= doesn't exist or access is restricted to it>
end
end
所以,为了稳健性和安全性,我希望能够这样写:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
# Only update two specific fields
product.update(params.slice(:name,:description))
# The above assumes a Hash (or Sinatra params) monkeypatch
# I will also accept standalone helper methods that perform the same
end
end
...而不是更冗长和非DRY选项:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
# Only update two specific fields
product.update({
name:params[:name],
description:params[:description]
})
end
end
更新:基准
以下是结果对(当前)实现进行基准测试:
user system total real
sawa2 0.250000 0.000000 0.250000 ( 0.269027)
phrogz2 0.280000 0.000000 0.280000 ( 0.275027)
sawa1 0.297000 0.000000 0.297000 ( 0.293029)
phrogz3 0.296000 0.000000 0.296000 ( 0.307031)
phrogz1 0.328000 0.000000 0.328000 ( 0.319032)
activesupport 0.639000 0.000000 0.639000 ( 0.657066)
mladen 1.716000 0.000000 1.716000 ( 1.725172)
@sawa 的第二个答案是最快的,比我基于 tap
的实现(基于他的第一个答案)领先一步。选择添加对 has_key?
的检查会增加很少的时间,但速度仍然是 ActiveSupport 的两倍多。
这是基准代码:
h1 = Hash[ ('a'..'z').zip(1..26) ]
keys = %w[a z c d g A x]
n = 60000
require 'benchmark'
Benchmark.bmbm do |x|
%w[ sawa2 phrogz2 sawa1 phrogz3 phrogz1 activesupport mladen ].each do |m|
x.report(m){ n.times{ h1.send(m,*keys) } }
end
end
Summary
Given a Hash, what is the most efficient way to create a subset Hash based on a list of keys to use?
h1 = { a:1, b:2, c:3 } # Given a hash...
p foo( h1, :a, :c, :d ) # ...create a method that...
#=> { :a=>1, :c=>3, :d=>nil } # ...returns specified keys...
#=> { :a=>1, :c=>3 } # ...or perhaps only keys that exist
Details
The Sequel database toolkit allows one to create or update a model instance by passing in a Hash:
foo = Product.create( hash_of_column_values )
foo.update( another_hash )
The Sinatra web framework makes available a Hash named params
that includes form variables, querystring parameters and also route matches.
If I create a form holding only fields named the same as the database columns and post it to this route, everything works very conveniently:
post "/create_product" do
new_product = Product.create params
redirect "/product/#{new_product.id}"
end
However, this is both fragile and dangerous. It's dangerous because a malicious hacker could post a form with columns not intended to be changed and have them updated. It's fragile because using the same form with this route will not work:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
product.update(params)
#=> <Sequel::Error: method foo= doesn't exist or access is restricted to it>
end
end
So, for robustness and security I want to be able to write this:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
# Only update two specific fields
product.update(params.slice(:name,:description))
# The above assumes a Hash (or Sinatra params) monkeypatch
# I will also accept standalone helper methods that perform the same
end
end
...instead of the more verbose and non-DRY option:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
# Only update two specific fields
product.update({
name:params[:name],
description:params[:description]
})
end
end
Update: Benchmarks
Here are the results of benchmarking the (current) implementations:
user system total real
sawa2 0.250000 0.000000 0.250000 ( 0.269027)
phrogz2 0.280000 0.000000 0.280000 ( 0.275027)
sawa1 0.297000 0.000000 0.297000 ( 0.293029)
phrogz3 0.296000 0.000000 0.296000 ( 0.307031)
phrogz1 0.328000 0.000000 0.328000 ( 0.319032)
activesupport 0.639000 0.000000 0.639000 ( 0.657066)
mladen 1.716000 0.000000 1.716000 ( 1.725172)
The second answer by @sawa is the fastest of all, a hair in front of my tap
-based implementation (based on his first answer). Choosing to add the check for has_key?
adds very little time, and is still more than twice as fast as ActiveSupport.
Here is the benchmark code:
h1 = Hash[ ('a'..'z').zip(1..26) ]
keys = %w[a z c d g A x]
n = 60000
require 'benchmark'
Benchmark.bmbm do |x|
%w[ sawa2 phrogz2 sawa1 phrogz3 phrogz1 activesupport mladen ].each do |m|
x.report(m){ n.times{ h1.send(m,*keys) } }
end
end
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我只会使用 active_support 提供的 slice 方法
当然,请确保更新您的 gemfile:
I would just use the slice method provided by active_support
Of course, make sure to update your gemfile:
我改变了主意。上一张好像不太好。
I changed by mind. The previous one doesn't seem to be any good.
Sequel 内置支持在更新时仅选择特定列:
但是,如果 :name 或 :description 不存在于参数中,则不会执行完全相同的操作。但假设您希望用户使用您的表单,那么这应该不是问题。
我总是可以扩展 update_fields 以获取选项哈希,其中包含一个选项,如果哈希中不存在该值,则该选项将跳过该值。我只是还没有收到这样做的请求。
Sequel has built-in support for only picking specific columns when updating:
That doesn't do exactly the same thing if :name or :description is not present in params, though. But assuming you are expecting the user to use your form, that shouldn't be an issue.
I could always expand update_fields to take an option hash with an option that will skip the value if not present in the hash. I just haven't received a request to do that yet.
也许
或者您可以复制 ActiveSupport 的
Hash#slice
,看起来更健壮一点。
Perhaps
Or you could just copy ActiveSupport's
Hash#slice
, it looks a bit more robust.这是我的实现;我将进行基准测试并接受更快(或更优雅)的解决方案:
Here are my implementations; I will benchmark and accept faster (or sufficiently more elegant) solutions: