Ruby allows mandatory and optional parameters to be defined in a flexible manner. These all work and are logical:
def foo(a); end
def foo(a, b); end
def foo(a, b=1); end
def foo(a=1, b); end
def foo(a=1, b=1); end
If you need three or four parameters, start considering whether you should switch to a different way of passing in the parameters. It's a maintenance and readability thing.
Instead of anything that looks like
def test(param1=nil, param2, param3, param4, param5, param6, param7, param8, param9, param10)
WAY before that point, switch to something more concise. Any of these would be preferable:
def foo(opts)
a, b, c = opts.values_at(*%i[a b c]).map{ |v| v.nil? ? 'nil' : v }
"a: '#{a}' b: '#{b}' c: '#{c}'"
end
foo(a:'a') # => "a: 'a' b: 'nil' c: 'nil'"
foo(a:'a', b:'b') # => "a: 'a' b: 'b' c: 'nil'"
foo(b:'b') # => "a: 'nil' b: 'b' c: 'nil'"
foo(a:'a', b:'b', c: 'c') # => "a: 'a' b: 'b' c: 'c'"
or:
Options = Struct.new('Options', 'a', 'b', 'c')
def foo(opts)
a, b, c = opts.values.map{ |v| v.nil? ? 'nil' : v }
"a: '#{a}' b: '#{b}' c: '#{c}'"
end
foo(Options.new('a', nil, 'c')) # => "a: 'a' b: 'nil' c: 'c'"
foo(Options.new('a', 'b', 'c')) # => "a: 'a' b: 'b' c: 'c'"
foo(Options.new(nil, 'b', 'c')) # => "a: 'nil' b: 'b' c: 'c'"
or:
require 'ostruct'
def foo(opts)
a, b, c = opts.to_h.values_at(*%i[a b c]).map{ |v| v.nil? ? 'nil' : v }
"a: '#{a}' b: '#{b}' c: '#{c}'"
end
foo(OpenStruct.new(:a => 'a')) # => "a: 'a' b: 'nil' c: 'nil'"
foo(OpenStruct.new(:a => 'a', :b => 'b')) # => "a: 'a' b: 'b' c: 'nil'"
foo(OpenStruct.new(:a => 'a', :b => 'b', :c => 'c')) # => "a: 'a' b: 'b' c: 'c'"
Using that sort of parameter passing greatly reduces the noise. Inside the method you can look for values that aren't initialized and force their default values and/or raise an error if you didn't receive a mandatory value.