1

I'm trying to figure out how to assign a default value for a hash key in my initialize method. I've made it work using a helper method to assign the date, but believe there has to be a more efficient way of doing this to avoid a separate helper method. Any thoughts appreciated!

def initialize(details)
  @name = details[:name]
  @age = details[:age]
  @admission_date = get_date(details)
end

def get_date(details)
  if details[:date].nil?
    @admission_date = Date.today.strftime("%d%m%y")
  else
    @admission_date = details[:date]
  end
end
1
  • I'm a bit confused Commented Sep 19, 2020 at 19:35

3 Answers 3

3

You're looking for keyword arguments:

class Thing
  def initialize(date: Date.today.strftime("%d%m%y"), **details)
    @name = details[:name]
    @age = details[:age]
    @admission_date = date
  end
end

The double splat (**) gathers the remaining keyword arguments into a hash.

You can create also create non optional-arguments by omitting the default value:

class Thing
  def initialize(name:, date: Date.today.strftime("%d%m%y"), **details)
    @name = name
    @age = details[:age]
    @admission_date = date
  end
end
Sign up to request clarification or add additional context in comments.

Comments

1

In Ruby, the conditional expression is, well, an expression, which means it evaluates to a value. (In Ruby, everything is an expression, there are no statements, ergo, everything evaluates to a value.) The value of a conditional expression is the value of the branch that was taken.

So, everywhere where you see something like this:

if foo
  bar(baz)
else
  bar(quux)
end

You can always replace that with

bar(
  if foo
    baz
  else
    quux
  end
)

# more conventionally written as

bar(if foo then baz else quux end)

And everywhere where you see something like this:

if foo
  bar = baz
else
  bar = quux
end

You can always replace that with

bar = if foo
  baz
else
  quux
end

So, let's do that here:

def get_date(details)
  @admission_date = if details[:date].nil?
    Date.today.strftime("%d%m%y")
  else
    details[:date]
  end
end

Let's look at this further: your get_date method is really strange. It performs a side-effect (it assigns to @admission_date) but it also returns a value. Normally, a method should either perform a side-effect (and return nothing, i.e. in Ruby nil) or return something but not both.

Also, the fact that a method named get something is actually setting something is incredibly confusing and misleading, and confusing and misleading method names are very dangerous because they lead to bugs where the programmer thinks the method is doing one thing but it actually does another.

I, personally, would not at all expect that it is unsafe to call a method named get something. But this method is unsafe: if I call it just to check what the current date is (after all, it is called get_date, so what could it possibly do other than, you know, get the date), I will actually overwrite the value of @admission_date!

What is even weirder, though, is the way that the method is used: even though the method already assigns the instance variable @admission_date when it is called, the return value of the method (which is just the value that was assigned to @admission_date) is used to assign @admission_date again, so that it immediately gets overwritten with the exact same value that it already has.

It seems obvious that even the author of the code was confused and mislead by the name of the method! The method name is so misleading that the author didn't even see the double assignment despite the fact that the two assignments are literally within 5 lines of each other.

So, let's remove one of the redundant assignments. I would prefer to remove the one in the get_date method to bring its behavior further in line with its name:

def get_date(details)
  if details[:date].nil?
    Date.today.strftime("%d%m%y")
  else
    details[:date]
  end
end

Furthermore, it looks like details[:date] can be either nil or a Date but it can never be false and nil is not a valid value. So, we can use the well-known || idiom here instead:

def get_date(details)
  details[:date] || Date.today.strftime("%d%m%y")
end

Or, probably even better, we can use Hash#fetch:

def get_date(details)
  details.fetch(:date, Date.today.strftime("%d%m%y"))
end

Since your helper method isn't really doing anything complex at this point, and is only called from one single place in your code, we can just inline it:

def initialize(details)
  @name = details[:name]
  @age = details[:age]
  @admission_date = details.fetch(:date, Date.today.strftime("%d%m%y"))
end

Note that since Ruby 2.0 (released on Ruby's 20th birthday in 2013), Ruby supports keyword parameters, both mandatory keyword parameters and optional keyword parameters with default argument values, so an even better way to write this might be:

def initialize(name:, age:, date: Date.today.strftime("%d%m%y"))
  @name, @age, @date = name, age, date
end

But that depends on your API design and your callers.

1 Comment

Thanks for the very through response Jorg. Clearly I'm learning naming conventions and assignment practices. This is very helpful.
1

Oh i now see, you can use the or sign, it'll return the first truthy value:

require 'date'

details1={name:"Will", age:19}
details2={name:"Will", age:19, date:'has_date'}

class Test
  attr_reader :admission_date, :age, :name
def initialize(details)
  @name = details[:name]
  @age = details[:age]
  @admission_date = details[:date]||Date.today.strftime("%d%m%y")
end
end

t1=Test.new(details1)
p t1.admission_date #"190920"

t2=Test.new(details2)
p t2.admission_date #"has_date"

5 Comments

Awesome, thanks PhiAgent! For greater understanding, why does it take the right side of the or statement? Is it because Ruby wants to return the truthiest value possible when given an option? (beginner Rubyist here).
No. || is the OR operator. It returns the first truthy value. Everything except false and nil is truthy in Ruby.
@ruby_newby: As a tipp, it is customary on Stack Exchange to wait at least 24h to a couple of days before accepting an answer, in order to give people in every timezone a chance to answer, and to encourage more thorough answers that may take a couple of hours or even days to write. (Seeing an accepted answer can discourage people from writing answers, plus, it removes the question from various "unanswered questions" lists and filters.)
So in agreement with @Max, the OR operator returns the first value that's truthy. In Ruby, only two values are not truthy: nil & false. In the case that date is not defined, details[:date] will evaluate to nil which is not truthy so it'll be skipped and the right_side returned. if details[:date] is defined, it'll be truthy and the left_side will be returned.
thanks for everyone's reponses and guidance on best practices on stack overflow. I'll give it some more time before accepting this answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.