Initialize ruby object with a hash with default values? - ruby

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

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

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.

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"

Related

Correct implementation and use of == operator in ruby

In continuation of another question I also have the following question.
I have a class which has a very central instance variable called var. It is so central to the class that when I print an object of the class, it should just print the instance variable.
When I compare that same object with a string which matches the instance variable, I would like it to return true, but that doesn't always work in the implementation below:
class MyClass
attr_accessor :var
def initialize(v)
#var = v
end
def to_s
#var.to_s
end
def inspect
#var.inspect
end
def ==(other)
self.var == other
end
# ... methods to manipulate #var
end
str = "test"
obj = MyClass.new("test")
puts obj # prints the var string - great this is what i want
p obj # prints the var string - great this is what i want
puts obj==str # returns true - great this is what i want
puts str==obj # returns false - oops! Fails since i didn't overload the == operator in String class. What to do? I don't think i want to "monkey patch" the String class.
I understand that it's not my overwritten == operator which is used. It's the one in the String class. What do you suggest I do to get around this? Am I going down the wrong path?
I read through the question What's the difference between equal?, eql?, ===, and ==? without getting an answer to my question.
I understand that it's not my overwritten == operator which is used. It's the one in the String class. What do you suggest I do to get around this? Am I going down the wrong path?
Before trying anything else, have a look at the docs for String#==:
If object is not an instance of String but responds to to_str, then the two strings are compared using object.==.
In other words: you have to implement to_str and return the object's string value:
class MyClass
# ...
def to_str
#var.to_s
end
end
You can also return to_s or use alias to_str to_s.
With any of the above, you'd get:
"test" == MyClass.new("test")
#=> true
Note that to_str should only be implemented by objects that are string-like.

Understanding self with method chaining

I'm trying to understand self in Ruby.
In the code pasted below, if I create a new instance of Animal with
fox = Animal.new.name("Fox").color("red").natural_habitat("forest").specie("mammal")
and then call
fox.to_s
It does not do anything if I do not return self in every method.
Why do I need self in every method? Isn't the variable already saved once I create a new Animal?
class Animal
def name(name)
#name = name
self
end
def specie(specie)
#specie = specie
self
end
def color(color)
#color = color
self
end
def natural_habitat(natural_habitat)
#natural_habitat = natural_habitat
self
end
def to_s
"Name: #{#name}, Specie: #{#specie}, Color: #{#color}, Natural Habitat: #{#natural_habitat}"
end
end
This pattern is used infrequently in Ruby, it's much more common in languages like Java and JavaScript, where it's notably rampant in jQuery. Part of the reason why is the verbosity you're describing here, and secondly because Ruby provides a convenient mutator generator in the form of attr_accessor or attr_writer.
One problem with these accessor/mutator dual purpose methods is ambiguity. The implementation you have is incomplete, you're unable to read from them. What you need is this:
def color(*color)
case (color.length)
when 1
#color = color
self
when 0
#color
else
raise ArgumentError, "wrong number of arguments (%d for 0)" % color.length
end
end
That's a whole lot of code to implement something that can be used in two ways:
animal.color('red')
animal_color = animal.color
If you want to use these, you'll need to write your own meta-programming method that can generate them, though I'd highly discourage going down that path in the first place. Use attr_accessor methods and an options Hash.
Here's the equivalent Ruby pattern:
# Up-front assignment via Hash
animal = Animal.new(
name: 'Fox',
color: 'red',
natural_habitat: 'forest',
specie: 'mammal'
)
# Assignment after the fact
animal.color = 'white'
In your example, using self as the return value is convenient. The methods return the instance itself, so that you can call:
fox = Animal.new
fox.name("Fox").color("red").natural_habitat("forest").specie("mammal")
The value of fox.name("Fox") is the instance itself, that's why you can call .color("red") on it.
if the #name method would be implemented without calling self, like so:
def name(name)
#name = name
end
This method would return a string when called.
Animal.new.name #=> returns "name of animal"
This means that
Animal.new.name.specie
would call #specie method on a string object (which probably raises NotImplemented error) instead of object of Animal class which implements the method.

What is the point of using "send" instead of a normal method call?

as far as I understand 'send' method, this
some_object.some_method("im an argument")
is same as this
some_object.send :some_method, "im an argument"
So what is the point using 'send' method?
It can come in handy if you don't know in advance the name of the method, when you're doing metaprogramming for example, you can have the name of the method in a variable and pass it to the send method.
It can also be used to call private methods, although this particular usage is not considered to be a good practice by most Ruby developers.
class Test
private
def my_private_method
puts "Yay"
end
end
t = Test.new
t.my_private_method # Error
t.send :my_private_method #Ok
You can use public_send though to only be able to call public methods.
In addition to Intrepidd's use cases, it is convenient when you want to route different methods on the same receiver and/or arguments. If you have some_object, and want to do different things on it depending on what foo is, then without send, you need to write like:
case foo
when blah_blah then some_object.do_this(*some_arguments)
when whatever then some_object.do_that(*some_arguments)
...
end
but if you have send, you can write
next_method =
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end
some_object.send(next_method, *some_arguments)
or
some_object.send(
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end,
*some_arguments
)
or by using a hash, even this:
NextMethod = {blah_blah: :do_this, whatever: :do_that, ...}
some_object.send(NextMethod[:foo], *some_arguments)
In addition to everyone else's answers, a good use case would be for iterating through methods that contain an incrementing digit.
class Something
def attribute_0
"foo"
end
def attribute_1
"bar"
end
def attribute_2
"baz"
end
end
thing = Something.new
3.times do |x|
puts thing.send("attribute_#{x}")
end
#=> foo
# bar
# baz
This may seem trivial, but it's occasionally helped me keep my Rails code and templates DRY. It's a very specific case, but I think it's a valid one.
The summing briefly up what was already said by colleagues: send method is a syntax sugar for meta-programming. The example below demonstrates the case when native calls to methods are likely impossible:
class Validator
def name
'Mozart'
end
def location
'Salzburg'
end
end
v = Validator.new
'%name% was born in %location%'.gsub (/%(?<mthd>\w+)%/) do
# v.send :"#{Regexp.last_match[:mthd]}"
v.send Regexp.last_match[:mthd].to_sym
end
=> "Mozart was born in Salzburg"
I like this costruction
Object.get_const("Foo").send(:bar)

Ruby: Automatically set instance variable as method argument?

Are there any plans to implement ruby behavior similar to the CoffeeScript feature of specifying an instance variable name in a method argument list?
Like
class User
def initialize(#name, age)
# #name is set implicitly, but #age isn't.
# the local variable "age" will be set, just like it currently works.
end
end
I'm aware of this question: in Ruby can I automatically populate instance variables somehow in the initialize method? , but all the solutions (including my own) don't seem to fit the ruby simplicity philosophy.
And, would there be any downsides for having this behavior?
UPDATE
One of the reasons for this is the DRY (don't repeat yourself) philosophy of the ruby community. I often find myself needing to repeat the name of an argument variable because I want it to be assigned to the instance variable of the same name.
def initialize(name)
# not DRY
#name = name
end
One downside I can think of is that it may look as though a method is doing nothing if it has no body. If you're scanning quickly, this may look like a no-op. But I think given time, we can adapt.
Another downside: if you're setting other instance variables in the body, and you try to be readable by putting all the assignments at the beginning, it can take more cognitive "power" to see that there assignments also happening in the argument list. But I don't think this is any harder than, say, seeing a constant or method call and having to jump to its definition.
# notice: instance var assignments are happening in 2 places!
def initialize(#name)
#errors = []
end
After some pondering, I wondered if it's possible to actually get the argument names from a ruby method. If so, I could use a special argument prefix like "iv_" to indicate which args should be set as instance variables.
And it is possible: How to get argument names using reflection.
Yes! So I can maybe write a module to handle this for me. Then I got stuck because if I call the module's helper method, it doesn't know the values of the arguments because they're local to the caller. Ah, but ruby has Binding objects.
Here's the module (ruby 1.9 only):
module InstanceVarsFromArgsSlurper
# arg_prefix must be a valid local variable name, and I strongly suggest
# ending it with an underscore for readability of the slurped args.
def self.enable_for(mod, arg_prefix)
raise ArgumentError, "invalid prefix name" if arg_prefix =~ /[^a-z0-9_]/i
mod.send(:include, self)
mod.instance_variable_set(:#instance_vars_from_args_slurper_prefix, arg_prefix.to_s)
end
def slurp_args(binding)
defined_prefix = self.class.instance_variable_get(:#instance_vars_from_args_slurper_prefix)
method_name = caller[0][/`.*?'/][1..-2]
param_names = method(method_name).parameters.map{|p| p.last.to_s }
param_names.each do |pname|
# starts with and longer than prefix
if pname.start_with?(defined_prefix) and (pname <=> defined_prefix) == 1
ivar_name = pname[defined_prefix.size .. -1]
eval "##{ivar_name} = #{pname}", binding
end
end
nil
end
end
And here's the usage:
class User
InstanceVarsFromArgsSlurper.enable_for(self, 'iv_')
def initialize(iv_name, age)
slurp_args(binding) # this line does all the heavy lifting
p [:iv_name, iv_name]
p [:age, age]
p [:#name, #name]
p [:#age, #age]
end
end
user = User.new("Methuselah", 969)
p user
Output:
[:iv_name, "Methuselah"]
[:age, 969]
[:#name, "Methuselah"]
[:#age, nil]
#<User:0x00000101089448 #name="Methuselah">
It doesn't let you have an empty method body, but it is DRY. I'm sure it can be enhanced further by merely specifying which methods should have this behavior (implemented via alias_method), rather than calling slurp_args in each method - the specification would have to be after all the methods are defined though.
Note that the module and helper method name could probably be improved. I just used the first thing that came to mind.
Well, actually...
class User
define_method(:initialize) { |#name| }
end
User.new(:name).instance_variable_get :#name
# => :name
Works in 1.8.7, but not in 1.9.3. Now, just where did I learn about this...
I think you answered your own question, it does not fit the ruby simplicity philosophy. It would add additional complexity for how parameters are handled in methods and moves the logic for managing variables up into the method parameters. I can see the argument that it makes the code less readable a toss up, but it does strike me as not very verbose.
Some scenarios the # param would have to contend with:
def initialize( first, last, #scope, #opts = {} )
def search( #query, condition )
def ratchet( #*arg )
Should all of these scenarios be valid? Just the initialize? The #*arg seems particularly dicey in my mind. All these rules and exclusions make the Ruby language more complicated. For the benefit of auto instance variables, I do not think it would be worth it.

Ruby: Boolean attribute naming convention and use

Learning ruby. I'm under the impression that boolean attributes should be named as follows:
my_boolean_attribute?
However, I get syntax errors when attempting to do the following:
class MyClass
attr_accessor :my_boolean_attribute?
def initialize
:my_boolean_attribute? = false
end
end
Apparently ruby is hating the "?". Is this the convention? What am I doing wrong?
Edit: three-years later; the times, they are a-changin'…
Julik's answer is the simplest and best way to tackle the problem these days:
class Foo
attr_accessor :dead
alias_method :dead?, :dead # will pick up the reader method
end
My answer to the original question follows, for posterity…
The short version:
You can't use a question mark in the name of an instance variable.
The longer version:
Take, for example, attr_accessor :foo — it's simply conceptually a bit of syntactic sugar for the following:
def foo
#foo
end
def foo=(newfoo)
#foo = newfoo
end
Furthermore, the question-mark suffix is mostly just a convention to indicate that the return value of a method is a boolean.
The best approximation I can make of what you're going for here…
class MyClass
def initialize
#awesome = true
end
def awesome?
#awesome
end
end
In this case, there may be a case to be made for using attr_accessor — after all, it may be explicit that you're working directly with a boolean attribute. Generally, I save the question-mark suffix for when I am implementing a method whose boolean return value is based on slightly more complex conditions than just the value of an attribute.
Cheers!
Edit, two years later, after a recent comment:
Ruby enforces certain naming conventions. Symbols in Ruby can't have question marks. Thus invocations of :my_boolean_attribute? both will fail with a NameError. Edit: not correct, just use the quoted syntax for a symbol, e.g., :"my_attribute?"
Symbols are immutable, attempting to assign to one will throw a SyntaxError.
The easiest way to quickly add a "question method" is to use aliasing for your reader method
class Foo
attr_accessor :dead
alias_method :dead?, :dead # will pick up the reader method
end
The attr_accessor symbol implies that the variable name is #my_boolean_attribute, so that's what you should be setting (not the symbol).
Also, you can't use ? for variables, just method names.
? is convention for methodnames, not variables. You can't use an instance variable named #foo?, however you could use a variable named #foo and name the (manually created) getter method foo? if you wanted to.
Monkey-patching metaprogramming - maybe it can be made more elegant, this is only a quick draft, and I haven't done metaprogramming for a little while...
# inject the convenience method into the definition of the Object class
class Object
def Object::bool_attr(attrname)
class_eval { define_method(attrname.to_s,
lambda { instance_variable_get('#' + attrname.to_s.chop) }) }
class_eval { define_method(attrname.to_s.chop+"=",
lambda { |x| instance_variable_set('#'+attrname.to_s.chop, x) }) }
end
end
### somewhere later
class MyClass
bool_attr :my_boolean_attribute?
def initialize
#my_boolean_attribute = true
end
end
# yet even more later
foo = MyClass.new
bar = MyClass.new
foo.my_boolean_attribute = 1
puts foo.my_boolean_attribute?
puts bar.my_boolean_attribute?
With this approach, you can be DRY and get the nice questionmark too. You just might need to pick a better name than "bool_attr", like, "bool_attr_accessor" or something similar.
The definitions that I made are a bit cranky, in a sense that the question mark is present in the original symbol. Probably a cleaner approach would be to avoid the questionmark in the symbol name and append it during the definition of the method - should be less confusing.
Oh, and almost forgot to include the obligatory link: Seeing metaclasses clearly
I looked through the answers, and while the accepted answer is on-target, it introduces "extra" noise in the class. The way I'd suggest solving this issue is:
class Animal
attr_writer :can_swim
def initialize(animal_type_name)
#can_swim = true
#animal_type_name = animal_type_name
end
def can_swim?
#can_swim
end
def to_s
#animal_type_name
end
end
dog = Animal.new('Dog in a bag')
dog.can_swim = false
puts "Can this #{dog} Swim? --- [#{dog_without_legs.can_swim? ? 'YEP!' : 'NOPE!'}]"

Resources