Sign up ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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'
  password = opts[:password]
  password ||= 'a_default_password'
  url = "jdbc:oracle:thin:#{userid}/#{password}@#{host_name}:1521:#{db_name}"
  $db = Sequel.connect(url)
end
share|improve this question

4 Answers 4

up vote 8 down vote accepted

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',
  password: 'a_default_password',
}

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

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

  url = "jdbc:oracle:thin:#{userid}/#{password}@#{host_name}:1521:#{db_name}"
  $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',
  password: 'a_default_password',
}

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[:password],
    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).

share|improve this answer
1  
.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

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

share|improve this answer

For the sake of completeness, I want to add fetch. 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.

As pointed out by user2748595, keyword arguments are a better option. They supersede fetch, and the options hash. But they are only available in Ruby 2. Required keyword arguments are only available in Ruby 2.1.

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")

I wrote two posts on keyword arguments and setting default options if you care for more detail.

share|improve this answer

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

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.