2

Following code:

class Test
  attr_reader :args
  def initialize(arg1={}, arg2: 'value2')
    @args = [arg1, arg2]
  end
end
t = Test.new({key1: 'value1'})
puts t.args

I've expected to get printed out array with content [{key1: 'value1'}, 'value2'] but I'm getting:

test.rb:11:in `new': unknown keywords: val1, val2 (ArgumentError)
    from test.rb:11:in `<main>'

To be more strange, with the same testing class invoked with {val1: 'value', val2: 'value2'}, arg2: 1) as arguments i'm getting output: {:val1=>"value", :val2=>"value2"}

Where is the source of this behaviour, i'm missing something or it's a bug? Ruby versions 2.1.1 and 2.1.2 was tested.

August
  • 12,410
  • 3
  • 35
  • 51
Karol Sikora
  • 522
  • 1
  • 4
  • 11

3 Answers3

4

I'm currently using Ruby 2.1.0p0.

The "problem" can be simplified a little with the following example:

def foo(arg1 = {}, arg2: 'value1')
  [arg1, arg2]
end

Here, the method foo has one OPTIONAL argument arg1 (with default {}) and one OPTIONAL keyword argument, arg2.

If you call:

foo({key1: 'value1'})

You get the error:

ArgumentError: unknown keyword: key1
        from (irb):17
        from /home/mark/.rvm/rubies/ruby-2.1.0/bin/irb:11:in `<main>'

The reason is that Ruby is attempting to match the only argument you gave (with keyword key1) to the only OPTIONAL keyword argument which is keyword arg2. They don't match, thus the error.

In the next example:

foo({val1: 'value', val2: 'value2'}, arg2: 1)

We get the result:

=> [{:val1=>"value", :val2=>"value2"}, 1]

This makes sense because I provided two arguments. Ruby can match arg2: 1 to the second keyword argument and accepts {val1: 'value', val2: 'value2'} as a substitute for the first optional argument.

I do not consider the behaviors above a bug.

lurker
  • 56,987
  • 9
  • 69
  • 103
  • "one REQUIRED keyword argument, arg2." In your example, `arg2` is **not** required. To make it required, you would have to omit its default value (`'value1'`). – Ajedi32 Aug 19 '14 at 18:41
  • @Ajedi32 ah right, thanks for the correction. I updated my answer accordingly. – lurker Aug 19 '14 at 18:42
2

Obviously, parameters resolution works the other way around from what you expected. In addition to konsolebox' answer, you can fix it by calling the constructor with an additional empty hash:

Test.new({key1: 'value1'}, {})
shock_one
  • 5,845
  • 3
  • 28
  • 39
-1

Do it this way instead:

class Test
  attr_reader :args
  def initialize(arg1={}, arg2 = 'value2')  ## Changed : to =.
    @args = [arg1, arg2]
  end
end
t = Test.new({key1: 'value1'})
puts t.args.inspect

Output:

[{:key1=>"value1"}, "value2"]
konsolebox
  • 72,135
  • 12
  • 99
  • 105