将参数散列切片以获得特定值

| 摘要 给定哈希,基于要使用的键列表创建子集哈希的最有效方法是什么?
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框架提供了一个名为“ 2”的哈希,其中包括表单变量,查询字符串参数以及路由匹配项。 如果我创建一个仅包含与数据库列名称相同的字段的表单并将其发布到此路由,则一切工作都非常方便:
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的第二个答案是所有答案中最快的,比我基于ѭ8implementation的实现要好(基于他的第一个答案)。选择添加“ 9”支票将花费很少的时间,但仍比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
    
已邀请:
我改变了主意。上一个似乎不太好。
class Hash
  def slice1(*keys)
    keys.each_with_object({}){|k, h| h[k] = self[k]}
  end
  def slice2(*keys)
    h = {}
    keys.each{|k| h[k] = self[k]}
    h
  end
end
    
我只会使用active_support提供的slice方法
require \'active_support/core_ext/hash/slice\'
{a: 1, b: 2, c: 3}.slice(:a, :c)                  # => {a: 1, c: 3}
当然,请确保更新您的gemfile:
gem \'active_support\'
    
Sequel具有内置支持,仅在更新时选择特定的列:
product.update_fields(params, [:name, :description])
如果:name或:description不存在于参数中,那将不会做完全相同的事情。但是假设您希望用户使用您的表单,那不应该成为问题。 我总是可以扩展update_fields以使用带有选项的选项哈希,如果哈希中不存在该选项,该选项将跳过该值。我只是还没有收到要求这样做。     
也许
class Hash
  def slice *keys
    select{|k| keys.member?(k)}
  end
end
或者,您也可以复制ActiveSupport的
Hash#slice
,它看起来更坚固。     
这是我的实现;我将进行基准测试并接受更快(或足够优雅)的解决方案:
# Implementation 1
class Hash
  def slice(*keys)
    Hash[keys.zip(values_at *keys)]
  end
end

# Implementation 2
class Hash
  def slice(*keys)
    {}.tap{ |h| keys.each{ |k| h[k]=self[k] } }
  end
end

# Implementation 3 - silently ignore keys not in the original
class Hash
  def slice(*keys)
    {}.tap{ |h| keys.each{ |k| h[k]=self[k] if has_key?(k) } }
  end
end
    

要回复问题请先登录注册