How to define a method in ruby using splat and an

2020-03-12 05:42发布

问题:

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?

回答1:

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.


回答2:

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!



回答3:

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}]  {}


回答4:

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]


回答5:

@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