# Setting defaults in a Ruby options hash

I have a specific method that I like because it lets me decide whether or not I want to use the default. If I want anything different I enter in :option => value otherwise I get the default. Here's a concrete example that works, but is a little ugly.

How can I accomplish the same thing in a more elegant manner?

def connect_to_oracle opts = {}
host_name = opts[:host_name]
host_name ||= 'a_default_host_name'
db_name = opts[:db_name]
db_name ||= 'a_default_db_name'
userid = opts[:userid]
userid ||= 'a_default_userid'
$db = Sequel.connect(url) end  ## 4 Answers You don't need the ||= you can use ||: def connect_to_oracle( opts = {} ) host_name = opts[:host_name] ||'a_default_host_name' db_name = opts[:db_name] || 'a_default_db_name' userid = opts[:userid] || 'a_default_userid' password = opts[:password] ||'a_default_password' url = "jdbc:oracle:thin:#{userid}/#{password}@#{host_name}:1521:#{db_name}"$db = Sequel.connect(url)
end


Another approach is Hash.merge:

DEFAULT = {
host_name: 'a_default_host_name',
db_name:  'a_default_db_name',
userid: 'a_default_userid',
}

def connect_to_oracle( opts = {} )
opts = DEFAULT.merge(opts)

host_name = opts[:host_name]
db_name = opts[:db_name]
userid = opts[:userid]

$db = Sequel.connect(url) end  or: DEFAULT = { host_name: 'a_default_host_name', db_name: 'a_default_db_name', userid: 'a_default_userid', password: 'a_default_password', } def connect_to_oracle( interface_opts = {} ) opts = DEFAULT.merge(interface_opts ) url = "jdbc:oracle:thin:%s/%s@%s:1521:%s" % [ opts[:userid], opts[:password], opts[:host_name], opts[:db_name], ]$db = Sequel.connect(url)
end
connect_to_oracle()
connect_to_oracle(:host_name => :xxxxxxxx)


I prefer the version with merge. So I get a constant with all default parameters in my documentation.

Another advantage: I can easily add checks if my interface contains correct keys.

Example:

DEFAULT = {
host_name: 'a_default_host_name',
db_name:  'a_default_db_name',
userid: 'a_default_userid',
}

def connect_to_oracle( myopts = {} )
(myopts.keys - DEFAULT.keys).each{|key|
puts "Undefined key #{key.inspect}"
}
opts = DEFAULT.merge(myopts)

url = "jdbc:oracle:thin:%s/%s@%s:1521:%s" % [
opts[:userid],
opts[:host_name],
opts[:db_name],
]
#~ \$db = Sequel.connect(url)
end
connect_to_oracle(:host_nam => :xxxxxxxx)


My method call contains an error (forgotten 'e'), but when you call it, you get a warning Undefined key host_nam. This often helps to detect errors. (in a real scenario I replace the puts with a logger-warning/error).

• .dup.update can also be expressed as .merge. – Wayne Conrad Jan 10 '14 at 20:35
• @WayneConrad Thanks. I already used it in the past, but it seems I forgot it again. I adapted my answer. – knut Jan 10 '14 at 20:45
• Be very careful with boolean hash parameters using the key = ops[:key] || default if you try to pass in false it will always default. One way around it would be to do something like key = ops[:key].nil? ? true : ops[:key] you could try to shorten this up by assuming that if they pass in that key it's going to be false rather than the default value, but then you run the risk that they pass in the same value as the default (assuming it's true) and you falsely presume it to be false. You could also invert your values so the default is false rather than true, but loose readability. – CTS_AE Jan 8 '18 at 23:42

Since Ruby 2.1 keyword arguments are the best option.

def initialize(person:, greeting: "Hello")
end


Here, person is required while greeting is not. If you don't pass person, you will get an ArgumentError: missing keyword: person.

The semantics of calling a method with keyword arguments are identical to a hash.

Greeter.new(greeting: "Hey", person: "Mohamad")


For older ruby versions

For older versions of Ruby (pre 2.1), one can use fetchfetch.

Using fetch has two advantages:

1. It lets you set default values
2. It raises an error if you don't specify a default
def initialize(options = {})
@greeting = options.fetch(:greeting, "Hello")
@person = options.fetch(:person)
end


If you attempt to instantiate an object without passing a :person, Ruby will raise an error. While :greeting will default to hello.

In raw Ruby, you can use Hash#merge. Since keys in the hash in argument will win, you can write that this way:

opts = {host_name: "a_default_host_name", db_name: "a_default_db_name"}.merge(opts)


If you use Rails framework, you can use the very convenient and elegant Hash#reverse_merge! method that edits the var itself (as the bang reminds you)

opts.reverse_merge!(host_name: "a_default_host_name", db_name: "a_default_db_name")


Note: You can use Active Support extensions (very useful little pieces of software that improves Ruby) without using all Rails framework, just load the extension you want, in this case it would be :

require 'active_support/core_ext/hash/reverse_merge'


Source: Rails Guides

If you are using Ruby 2.0 or better you can use keyword arguments to accomplish this very cleanly. In the past (pre 2.0) I have used a custom helper method to duplicate this functionality. The call to the helper would look like this:

opts.set_default_keys!({your: "default", values: "go here"})


The helper would be a relatively safe monkey patch on Hash and would only replace the unset keys with the default values and leave the existing values as they are.

For documentation on keyword arguments in Ruby >= 2.0 visit rdoc.org - methods.rdoc