Note that there are some explanatory texts on larger screens.

plurals
  1. POSlicing params hash for specific values
    primarykey
    data
    text
    <h2>Summary</h2> <p>Given a Hash, what is the most efficient way to create a subset Hash based on a list of keys to use?</p> <pre><code>h1 = { a:1, b:2, c:3 } # Given a hash... p foo( h1, :a, :c, :d ) # ...create a method that... #=&gt; { :a=&gt;1, :c=&gt;3, :d=&gt;nil } # ...returns specified keys... #=&gt; { :a=&gt;1, :c=&gt;3 } # ...or perhaps only keys that exist </code></pre> <h2>Details</h2> <p>The <a href="http://sequel.rubyforge.org/index.html" rel="noreferrer">Sequel</a> database toolkit allows one to create or update a model instance by passing in a Hash:</p> <pre><code>foo = Product.create( hash_of_column_values ) foo.update( another_hash ) </code></pre> <p>The <a href="http://www.sinatrarb.com/" rel="noreferrer">Sinatra</a> web framework makes available a Hash named <code>params</code> that includes form variables, querystring parameters and also route matches.</p> <p>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:</p> <pre><code>post "/create_product" do new_product = Product.create params redirect "/product/#{new_product.id}" end </code></pre> <p>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:</p> <pre><code>post "/update_product/:foo" do |prod_id| if product = Product[prod_id] product.update(params) #=&gt; &lt;Sequel::Error: method foo= doesn't exist or access is restricted to it&gt; end end </code></pre> <p>So, for robustness and security I want to be able to write this:</p> <pre><code>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 </code></pre> <p>...instead of the more verbose and non-DRY option:</p> <pre><code>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 </code></pre> <h2>Update: Benchmarks</h2> <p>Here are the results of benchmarking the (current) implementations:</p> <pre class="lang-none prettyprint-override"><code> 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) </code></pre> <p>The second answer by @sawa is the fastest of all, a hair in front of my <code>tap</code>-based implementation (based on his first answer). Choosing to add the check for <code>has_key?</code> adds very little time, and is still more than twice as fast as ActiveSupport.</p> <p>Here is the benchmark code:</p> <pre><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 </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload