This question already has an answer here:
- Optional argument after splat argument 6 answers
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?
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.
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.
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}] {}
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]
@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
来源:https://stackoverflow.com/questions/13960678/how-to-define-a-method-in-ruby-using-splat-and-an-optional-hash-at-the-same-time