Ответ 1
Я передумал. Предыдущий не кажется хорошим.
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
Учитывая хеш, какой наиболее эффективный способ создать подмножество Hash на основе списка ключей для использования?
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 позволяет создать или обновить экземпляр модели, передав в Hash:
foo = Product.create( hash_of_column_values )
foo.update( another_hash )
Sinatra веб-каркас предоставляет доступ к Hash с именем 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
... вместо более подробного и не суточного варианта:
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
Я передумал. Предыдущий не кажется хорошим.
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
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