10

I am able to define a method like this:

def test(id, *ary, hash_params)
  # Do stuff here
end

But this makes the hash_params argument mandatory. These don't work either:

def t(id, *ary, hash_params=nil)  # SyntaxError: unexpected '=', expecting ')'
def t(id, *ary, hash_params={})   # SyntaxError: unexpected '=', expecting ')'

Is there a way to make it optional?

andersonvom
  • 11,701
  • 4
  • 35
  • 40
  • Which version of Ruby are you using? – the Tin Man Dec 19 '12 at 21:14
  • The question is not clear. If you do `test(id, hash1)`, are you expecting `ary = [hash1]; hash = nil` or `ary = []; hash = hash1`? What is the rule that determines whichever to choose? – sawa Dec 20 '12 at 01:00

5 Answers5

13

There is support for this in ActiveSupport through the use of the array extension extract_options!.

def test(*args)
  opts = args.extract_options!
end

If the last element is a hash, then it will pop it from the array and return it, otherwise it will return an empty hash, which is technically the same as what you want (*args, opts={}) to do.

ActiveSupport Array#extract_options!

Cluster
  • 5,457
  • 1
  • 25
  • 36
11

You can't do that. You have to think about how Ruby would be able to determine what belongs to *ary and what belongs to the optional hash. Since Ruby can't read your mind, the above argument combination (splat + optional) is impossible for it to solve logically.

You either have to rearrange your arguments:

def test(id, h, *a)

In which case h will not be optional. Or then program it manually:

def test(id, *a)
  h = a.last.is_a?(Hash) ? a.pop : nil
  # ^^ Or whatever rule you see as appropriate to determine if h
  # should be assigned a value or not.
Casper
  • 33,403
  • 4
  • 84
  • 79
4

In addition to caspers answer:

You may use a splash parameter and check, if the last parameter is a hash. Then you take the hash as settings.

A code example:

def test(id, *ary )
  if ary.last.is_a?(Hash)
    hash_params =   ary.pop
  else
    hash_params =   {}
  end
  # Do stuff here

  puts "#{id}:\t#{ary.inspect}\t#{hash_params.inspect}"
end


test(1, :a, :b )
test(2, :a, :b, :p1 => 1, :p2 => 2 )
test(3, :a,  :p1 => 1, :p2 => 2 )

Result is:

1:  [:a, :b]    {}
2:  [:a, :b]    {:p1=>1, :p2=>2}
3:  [:a]    {:p1=>1, :p2=>2}

This will make problems, if your array-parameter should contain a hash at last position.

test(5, :a,  {:p1 => 1, :p2 => 2} )
test(6, :a,  {:p1 => 1, :p2 => 2}, {} )

Result:

5:  [:a]    {:p1=>1, :p2=>2}
6:  [:a, {:p1=>1, :p2=>2}]  {}
Community
  • 1
  • 1
knut
  • 27,320
  • 6
  • 84
  • 112
2

You could (mis-)use the optional block at the end of the parameter list:

def test(id,*ary, &block)
  if block_given?
    opt_hash = block.call
    p opt_hash
  end
  p id
  p ary
end

test(1,2,3){{:a=>1,:b=>2}}
# output:
# {:a=>1, :b=>2} #Hurray!
# 1
# [2, 3]
steenslag
  • 79,051
  • 16
  • 138
  • 171
1

@Casper is right. Only one of the parameters can have the splat operator. Arguments get assigned to the non-splatted parameters first left-to-right. Remaining arguments get assigned to the splat parameter.

You can do as he suggest. You can also do this:

def test(id,h={},*a)
  # do something with id
  # if not h.empty? then do something with h end
  # if not a.empty? then do something with a end
end

Here are some sample irb runs:

001 > def test (id, h={}, *a)
002?>   puts id.inspect
003?>   puts h.inspect
004?>   puts a.inspect
005?>   end
 => nil 
006 > test(1,2,3,4)
1
2
[3, 4]
 => nil 
007 > test(1,{"a"=>1,"b"=>2},3,4)
1
{"a"=>1, "b"=>2}
[3, 4]
 => nil 
008 > test(1,nil,3,4)
1
nil
[3, 4]
 => nil

Perhaps, I should add. You CAN have an optional parameter as the last parameter but it must be a block/proc.

For example:

def test(a,*b, &c)
  puts a.inspect
  puts b.inspect
  c.call if not c.nil?
end

Here are some sample calls:

006 > test(1,2,3)
1
[2, 3]
 => nil 
007 > test(1,2,3) {puts "hello"}
1
[2, 3]
hello
 => nil 
Martin Velez
  • 1,379
  • 11
  • 24
  • Yeah true. Although `h=nil` or `h={}` only makes sense if you're going to call `test` with `test(123)`, because in all other cases you have to provide the value to `h` manually when calling `test`. Ruby won't accept `test(123,,somehing,else)`. – Casper Dec 19 '12 at 21:11
  • 1
    Based on his question, `test(123)` might be a valid call. – Martin Velez Dec 19 '12 at 21:14