Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does double splat only work with symbol keys?

The double splat operator ** only seems to work with hashes whose keys are symbols. It will not work when a key is a string, for example. This is true for both ways; for construction:

def foo **; end
foo(:a => 3) #=> nil
foo("a" => 3) #=> ArgumentError: wrong number of arguments (1 for 0)

and destruction:

def bar *; end
bar(**{:a => 3}) #=> nil
bar(**{"a" => 3}) #=> TypeError: wrong argument type String (expected Symbol)

Why is it limited to symbol keys?

It may be related to the fact that named keyword notation a: 3 coincides with the syntax sugar for hash with symbol keys, but as seen above, the double splat works with the ordinary hash notation :a => 3, so I am not sure about this.

like image 422
sawa Avatar asked Aug 07 '14 20:08

sawa


2 Answers

I ran into something like this recently.

If you're in Rails and you have a method that takes keyword arguments and you have a strong params hash that you want to send to it, you can use symbolize_keys on the params hash and it will properly separate out the arguments, no double splat needed.

Model

class ContactForm
  def initialize(name:, email:)
    @name = name
    @email = email
  end
  # Other stuff
end

Controller

class ContactController < ApplicationController
  def send_mail
    @contact_form = ContactForm.new(contact_params)
    if @contact_form.submit
      # Do stuff
    end
  end

  def contact_params
    params.require(:contact_form).permit(:name, :email).symbolize_keys
  end
end
like image 153
flanger001 Avatar answered Nov 10 '22 21:11

flanger001


The short answer: that's how keyword arguments, which the double-splat operator is supposed to capture, are expressed in Ruby.

The long answer: the double-splat operator is for capturing keywords as seen in this answer: https://stackoverflow.com/a/18289218/3644699

Since Ruby 2.0, Ruby supports true keyword arguments. My guess is that in the main implementation they are still represented as Hashes whose keys are symbols, similar to how they were simulated before the language officually supported them.

The particular errors you're getting likely depend on implementation. For example, executing the latter code snippet in irb shows the function that raises the TypeError:

2.1.2 :001 > def bar *; end
 => :bar 
2.1.2 :002 > bar(**{"a" => 3})
TypeError: wrong argument type String (expected Symbol)
    from (irb):2:in `core#hash_merge_kwd'
    from (irb):2
    from /home/mkis/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
2.1.2 :003 > 

hash_merge_kwd is an internal function, defined here: https://github.com/ruby/ruby/blob/d738e3e15533e0f500789faaedcef9ed9ca362b9/vm.c#L2398

like image 8
mkis Avatar answered Nov 10 '22 21:11

mkis