Reputation: 25
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
Upvotes: 1
Views: 583
Reputation: 158
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"
Upvotes: 1
Reputation: 369458
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.
Upvotes: 1
Reputation: 102036
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
Upvotes: 3