No named parameters in Ruby?
What's actually happening:
# Assign a value of "meh" to scope, which is OUTSIDE meth and equivalent to
# scope = "meth"
# meth(1, scope)
meth(1, scope = "meh")
# Ruby takes the return value of assignment to scope, which is "meh"
# If you were to run `puts scope` at this point you would get "meh"
meth(1, "meh")
# id = 1, options = "meh", scope = "scope"
puts options
# => "meh"
There is no support* for named parameters (see below for 2.0 update). What you're seeing is just the result of assigning "meh"
to scope
being passed as the options
value in meth
. The value of that assignment, of course, is "meh"
.
There are several ways of doing it:
def meth(id, opts = {})
# Method 1
options = opts[:options] || "options"
scope = opts[:scope] || "scope"
# Method 2
opts = { :options => "options", :scope => "scope" }.merge(opts)
# Method 3, for setting instance variables
opts.each do |key, value|
instance_variable_set "@#{key}", value
# or, if you have setter methods
send "#{key}=", value
end
@options ||= "options"
@scope ||= "scope"
end
# Then you can call it with either of these:
meth 1, :scope => "meh"
meth 1, scope: "meh"
And so on. They're all workarounds, though, for the lack of named parameters.
Edit (February 15, 2013):
* Well, at least until the upcoming Ruby 2.0, which supports keyword arguments! As of this writing it's on release candidate 2, the last before the official release. Although you'll need to know the methods above to work with 1.8.7, 1.9.3, etc., those able to work with newer versions now have the following option:
def meth(id, options: "options", scope: "scope")
puts options
end
meth 1, scope: "meh"
# => "options"
Named parameters in Ruby 2
The last example you posted is misleading. I disagree that the behavior is similar to the one before. The last example passes the argument hash in as the first optional parameter, which is a different thing!
If you do not want to have a default value, you can use nil
.
If you want to read a good writeup, see "Ruby 2 Keyword Arguments".
Can I have required named parameters in Ruby 2.x?
There is no specific way in Ruby 2.0.0, but you can do it Ruby 2.1.0, with syntax like def foo(a:, b:) ...
In Ruby 2.0.x, you can enforce it by placing any expression raising an exception, e.g.:
def say(greeting: raise "greeting is required")
# ...
end
If you plan on doing this a lot (and can't use Ruby 2.1+), you could use a helper method like:
def required
method = caller_locations(1,1)[0].label
raise ArgumentError,
"A required keyword argument was not specified when calling '#{method}'"
end
def say(greeting: required)
# ...
end
say # => A required keyword argument was not specified when calling 'say'
How can Ruby functions with named arguments get called with a hash instead?
Just call the method with the hash: foo(hash)
The bigger problem: You didn't use named parameters (or better keyword arguments) but parameters with default values. To use named parameters you must not not use =
but :
.
def foo(x: 'hi', y:'this is y')
puts x
puts y
end
hash = {x: 'first', y: 'second'}
foo(hash)
How to pass optional named arguments in Ruby 2.0
Probably the best way to go would be:
def bar(name: nil)
name ||= 'unknown'
puts name
end
Passing `nil` to method using default named parameters
If you want to follow a Object-Oriented approach, you could isolate your defaults in a separate method and then use Hash#merge
:
class Taco
def initialize (args)
args = defaults.merge(args)
@meat = args[:meat]
@cheese = args[:cheese]
@salsa = args[:salsa]
end
def assemble
"taco with: #{@meat} + #{@cheese} + #{@salsa}"
end
def defaults
{meat: 'steak', cheese: true, salsa: 'spicy'}
end
end
Then following the suggestion by @sawa (thanks), use Rails' Hash#compact
for your input hashes that have explicitly defined nil
values and you will have the following output:
taco with: chicken + false + mild
taco with: steak + true + spicy
taco with: pork + true + spicy
EDIT:
If you do not want to use Rails' wonderful Hash#compact
method, you can use Ruby's Array#compact
method. Replacing the first line within the initialize
method to:
args = defaults.merge(args.map{|k, v| [k,v] if v != nil }.compact.to_h)
Why does specifing a named parameter change the parameters type
Unlike in other languages (such as Python), Ruby strictly separates positional arguments and keyword arguments. You can't provide a keyword parameter as a positional argument when calling a method and vice-versa.
In your case, you have define a single positional parameter in your method (namely b
). In your last two examples, you are passing a Hash to it. Note that the curly braces you generally use to define a hash are optional when calling methods and passing a Hash as its last argument. In older Ruby versions, this was used as a convention to allow/pass an optional list of arguments which works similar to keyword arguments.
As a Hash object is always truthy (remember that only false
and nil
are falsey in Ruby), you are always using the if
branch of your conditional and outputting the information you see there.
With that being said, if you want to accept keyword arguments with your method, you have to define it accordingly, e.g.:
def test_param(b: false)
# ...
end
Note the difference in the parameter definition here. You can learn more about keyword arguments in Ruby from the Ruby language documentation.
How do I add named parameters in a subclass or change their default in Ruby 2.2?
The problem here is that since the parent doesn't know anything about the child's arguments, it has no way of knowing whether the first argument you pass to it was meant to be a positional argument, or whether it was intended to provide keyword arguments to the parent method. This is because of a historical feature where Ruby allowed hashes to be passed as keyword-argument style parameters. For example:
def some_method(options={})
puts options.inspect
end
some_method(arg1: "Some argument", arg2: "Some other argument")
Prints:
{:arg1=>"Some argument", :arg2=>"Some other argument"}
If Ruby disallowed that syntax (which would break backwards compatibility with existing programs), you could write your child method like this using the double splat operator:
class Child < Parent
def foo(*args, named1: "child", named2: "child", **keyword_args)
puts "Passing to parent: #{[*args, named1: named1, **keyword_args].inspect}"
super(*args, named1: named1, **keyword_args)
end
end
In fact, this works fine when you pass keyword arguments in addition to the positional one:
Child.new.foo({ this: 23 }, named2: "caller")
Prints:
Passing to parent: [{:this=>23}, {:named1=>"child"}]
{:this=>23}
"child"
"parent"
However, since Ruby can't tell the difference between positional arguments and keyword arguments when you only pass a single hash, Child.new.foo({ this: 23 })
results in this: 23
getting interpreted as a keyword argument by the child, and the parent method ends up interpreting both keyword arguments forwarded to it as a single positional argument (a hash) instead:
Child.new.foo({this: 23})
Prints:
Passing to parent: [{:named1=>"child", :this=>23}]
{:named1=>"child", :this=>23}
"parent"
"parent"
There are a few ways you can fix this but none of them are exactly ideal.
Solution 1
As you tried to do in your third example, you could tell the child that the first argument passed will always be a positional argument, and that the rest will be keyword args:
class Child < Parent
def foo(arg, named1: "child", named2: "child", **keyword_args)
puts "Passing to parent: #{[arg, named1: named1, **keyword_args].inspect}"
super(arg, named1: named1, **keyword_args)
end
end
Child.new.foo({this: 23})
Child.new.foo({this: 23}, named1: "custom")
Prints:
Passing to parent: [{:this=>23}, {:named1=>"child"}]
{:this=>23}
"child"
"parent"
Passing to parent: [{:this=>23}, {:named1=>"custom"}]
{:this=>23}
"custom"
"parent"
Solution 2
Switch entirely to using named arguments. This avoids the problem entirely:
class Parent
def foo(positional:, named1: "parent", named2: "parent")
puts positional.inspect
puts named1.inspect
puts named2.inspect
end
end
class Child < Parent
def foo(named1: "child", named3: "child", **args)
super(**args, named1: named1)
end
end
Child.new.foo(positional: {this: 23})
Child.new.foo(positional: {this: 23}, named2: "custom")
Prints:
{:this=>23}
"child"
"parent"
{:this=>23}
"child"
"custom"
Solution 3
Write some code to figure everything out programmatically.
This solution would likely be pretty complex and would depend a lot on exactly how you want it to work, but the idea is that you would use Module#instance_method
, and UnboundMethod#parameters
to read the signature of the parent's foo method and pass arguments to it accordingly. Unless you really need to do it this way, I'd recommend using one of the other solutions instead.
Named parameters in Ruby don't work?
In ruby my_method(999, var3 = 123)
means, assign var3
the value 123
and pass it to my_method
.
There is no concept of named parameters, however, you can use a hash as an argument to my_method
such as:
def my_method(args = {})
p "#{args[:a]}, #{args[:b]}, #{args[:c]}"
end
Then you can call it with:
my_method(b: 2, c: 3, a: 1)
which will print 1, 2, 3
, because b: 2, c: 3, a: 1
is inferred to be a hash by ruby. You can explicitly indicate the hash as well with:
my_method({b: 2, c: 3, a: 1})
Related Topics
How to Find If a String Starts with Another String in Ruby
Rspec: How to Stub an Instance Method Called by Constructor
Get Class Location from Class Object
Rails Erb Form Helper Options_For_Select :Selected
Hash Ordering Preserved Between Iterations If Not Modified
How to Remove a Substring After a Certain Character in a String Using Ruby
Emacs Ruby-Mode Indentation Behavior
Stack Level Too Deep Error in Ruby on Rails
Passenger: Internal Server Error
How to Use "Puts" to the Console Without a Line Break in Ruby on Rails
How to Get the Line of Code That Triggers a Query
Put Haml Tags Inside Link_To Helper
Ruby - Determining Method Origins