In Java you can overload constructors:
public Person(String name) {
this.name = name;
}
public Person(String firstName, String lastName) {
this(firstName + " " + lastName);
}
Is there a way in Ruby to achieve this same result: two constructors that take different arguments?
The answer is both Yes and No.
You can achieve the same result as you can in other languages using a variety of mechanisms including:
Default values for arguments
Variable Argument lists (The splat operator)
Defining your argument as a hash
The actual syntax of the language does not allow you to define a method twice, even if the arguments are different.
Considering the three options above these could be implemented with your example as follows
# As written by #Justice
class Person
def initialize(name, lastName = nil)
name = name + " " + lastName unless lastName.nil?
#name = name
end
end
class Person
def initialize(args)
name = args["name"]
name = name + " " + args["lastName"] unless args["lastName"].nil?
#name = name
end
end
class Person
def initialize(*args)
#Process args (An array)
end
end
You will encounter the second mechanism frequently within Ruby code, particularly within Rails as it offers the best of both worlds and allows for some syntactic sugar to produce pretty code, particularly not having to enclose the passed hash within braces.
This wikibooks link provides some more reading
I tend to do
class Person
def self.new_using_both_names(first_name, last_name)
self.new([first_name, last_name].join(" "))
end
def self.new_using_single_name(single_name)
self.new(single_name)
end
def initialize(name)
#name = name
end
end
But I don't know if this is the best approach.
class Person
def initialize(name, lastName = nil)
name = name + " " + lastName unless lastName.nil?
#name = name
end
end
class StatementItem
attr_reader :category, :id, :time, :amount
def initialize(item)
case item
when Order
initialize_with_order(item)
when Transaction
initialize_with_transaction(item)
end
end
def valid?
!(#category && #id && #time && #amount).nil?
end
private
def initialize_with_order(order)
return nil if order.status != 'completed'
#category = 'order'
#id = order.id
#time = order.updated_at
#amount = order.price
end
def initialize_with_transaction(transaction)
#category = transaction.category
#id = transaction.id
#time = transaction.updated_at
#amount = transaction.amount
end
end
You can use konstructor gem to declare multiple constructors in Ruby and imitate overloading:
class Person
def initialize(name)
#name = name
end
konstructor
def from_two_names(first_name, last_name)
#name = first_name + ' ' + last_name
end
end
Person.new('John Doe')
Person.from_two_names('John', 'Doe')
You could use the double splat operator ** in conjunction with logical or (double pipes) || inside the initialize method to achieve the same effect.
class Person
def initialize(**options)
#name = options[:name] || options[:first_name] << ' ' << options[:last_name]
end
end
james = Person.new(name: 'James')
#=> #<Person #name="James">
jill_masterson = Person.new(first_name: 'Jill', last_name: 'Masterson')
#=> #<Person #name="Jill Masterson">
However, if a new Person is created without a first_name, then the append << operation will fail with NoMethodError: undefined method '<<' for nil:NilClass. Here is a refactored initialize method to handle this case (using strip to remove whitespace if either option is excluded).
class Person
def initialize(**options)
#name = options[:name] || [ options[:first_name] , options[:last_name] ].join(' ').strip
end
end
goldfinger = Person.new(last_name: 'Goldfinger')
#=> #<Person #name="Goldfinger">
oddjob = Person.new(first_name: 'Oddjob')
#=> #<Person #name="Oddjob">
In fact, this approach handles calling Person.new without arguments or with an unexpected key to return the new instance with #name set to an empty string:
nameless = Person.new
#=> <#Person #name="">
middle_malcom = Person.new(middle_name: 'Malcom')
#=> <#Person #name="">
checkout functional-ruby gem which is inspired by Elixir pattern matching features.
class Person
include Functional::PatternMatching
defn(:initialize, String) { |name|
#name = name
}
defn(:initialize, String, String) {|first_name, last_name|
#name = first_name + ' ' + last_name
}
end
Related
Take this for example:
class Inner
attr_accessor :id, :number
def initialize(id, number)
#id = id
#number = number
end
def id()
return #id + "_" #number
end
end
class Outer
attr_accessor :id, :inner, :another
def initialize(id, inner, another)
#id = id
#inner = inner # instance variable for class Inner object
#another = another
end
def id()
return "Outer id:\n"
+ #id + "\nInner : " + #inner.id() +"\nAnother: " + #another + "\n"
end
end
When I call the id() function of an "Outer" object, the output stops at "\nInner : ". How do I get around this? Thank you.
You should add on line 8 a + and also cast the integer value to a string with to_s
return #id.to_s + "_" + #number.to_s
On line 20 shouldn't be a line break, just put everything after return all on one line. There is also the same issue as it was on line 8. You have to cast the integer with .to_s to a string or put "#{}" around them.
return "Outer id:\n" + "#{#id}" + "\nInner : " + "#{#inner.id}" +"\nAnother: " + "#{#another}"+ "\n"
Rewrite of the code for more idiomatic ruby:
class Inner
attr_accessor :id, :number
def initialize(id, number)
#id = id
#number = number
end
def to_s
"#{id}_#{number}"
end
end
class Outer
attr_accessor :id, :inner, :another
def initialize(id, inner, another)
#id = id
#inner = inner # instance variable for class Inner object
#another = another
end
def to_s
<<~STR
Outer id:#{id}
Inner : #{inner}
Another: #{another}
STR
# Or something like
# [
# "Outer id:#{id}",
# "Inner : #{inner}",
# "Another: #{another}",
# ].join("\n") + "\n"
end
end
Relevant styleguide
Prefer "#{var}" over '' + var + '' for string interpolation to automatically cast variables as strings.
Don't use explicit return unless needed.
Prefer heredoc for multi-line strings (or other patterns).
Don't define an attr_acessor then redefine the method in the class. Generally I'd avoid defining a getter that doesn't return the stored variable. It looks like you want to a "stringified" representation of the class, which is commonly done with a to_s method.
Prefer instance methods over ivars.**
** This is probably more personal preference, but I find code with ivars as less refactorable than code that uses the accessor methods (since the code works the same whether it's a method argument or instance method or local variable)
I need to randomly pick a name from an array in Ruby and then check if it uppercase. So far I have:
def namegenerator
return #name.sample
end
def namechecker
if name.upcase then
check = TRUE
else
check = FALSE
end
end
It needs to be as two separate methods like this.
Something like this:
def sample_word(words)
words.sample
end
def upcase?(word)
word == word.upcase
end
And then something like:
words = %w[APPLE banana CherRy GRAPE]
word = sample_word(words)
puts word # e.g. BANANA
puts upcase?(word) # will print true
If you just want to check just the first letter:
names = %w(Kirk mccoy scott Spock)
names.sample.then { |name| [name, name[0] == name[0].upcase] }
#=> ["mccoy", false]
Maybe something like this:
class NameGenerator
def initialize(size, items)
#name = ""
#size = size
#items = items
end
def namegenerator
#name = #items.sample(#size).to_s
end
def namechecker?
#name == #name.upcase
end
def name
#name
end
end
ng = NameGenerator.new 1, ["name", "Name", "NAME"]
ng.namegenerator
puts ng.name, ng.namechecker?
Update
I've posted code without much thinking about abstraction and i think it would be much better to encapsulate name and upper case check to separate class and make it immutable, then make generator class that selects one entity from collection.
class NameGenerator
def initialize(items)
#items = items
end
def next
#items.sample
end
end
class Name
attr_reader :name
def initialize(name)
#name = name
end
def is_uppercase?
#name.match(/\p{Lower}/) == nil
end
end
ng = NameGenerator.new [
Name.new("name"),
Name.new("Name"),
Name.new("NAME"),
Name.new("na-me")
]
name = ng.next
puts name.name, name.is_uppercase?
Working on a Ruby program I was looking to move some state data from instance variables to class variables, it dawned on me that while instance variables are auto-vivified (if you try to read them "without initializing" them, they are automatically initialized to nil), class variables are not - and this looks very inconsistent to me (compared to most Ruby syntax which is very consistent).
Sample program:
class Test
def id
#id.to_i
end
def id=(i)
#id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid #=> 1
puts t.nextid #=> 2
In which case, when calling Test::id, if #id was not initialized, Ruby will auto-vivify it to nil (after which I to_i it to get 0).
Now I decide that I want the running ID to be shared across Test instance, so I rewrite it like this:
class Test
def id
##id.to_i
end
def id=(i)
##id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid
puts t.nextid
Should work the same, I thought, but no:
NameError: uninitialized class variable ##id in Test
But this workaround works (!?) :
class Test
def id
(##id ||= 0).to_i
end
def id=(i)
##id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid #=> 1
puts t.nextid #=> 2
(granted, after doing lazy init to 0 I can drop the to_i, but I left it for consistency).
It looks like Ruby understands "lazy initialization" and treats it as the magic needed to not throw NameError - even though ||= is supposedly just syntactic sugar to x = x || val (which BTW doesn't work for initing class variables, thanks for asking).
How come?
Class variables initialization
Here's a possible explanation why #a is nil but ##a is a NameError.
But if you want to use class variables, you should initialize them inside the class, not inside instance methods :
class Test
##id = 0
def id
##id
end
def id=(i)
##id = i
end
def nextid
self.id = id + 1
end
end
t = Test.new
puts t.nextid
puts t.nextid
Please note that it doesn't make much sense to have an instance setter method for a class variable.
Class instance variables
To avoid mixing instance methods and class variables, you could define everything at the class level with a "class instance variable". It's an instance variable defined at the class level:
class Test
#id = 0
class << self
def id
#id
end
def id=(i)
#id = i
end
def nextid
self.id = id + 1
end
end
end
puts Test.id
# 0
puts Test.nextid
# 1
puts Test.nextid
# 2
It means you could just use attr_accesor:
class Test
#id = 0
class << self
attr_accessor :id
def nextid
self.id = id + 1
end
end
end
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names
I'm convinced I've had this working before!
I have the following class. The title string is created within the class the fname and lname strings are passed in as parameters. However, I can't seem to ever get the #title object to return anything other than nil.
What am I doing wrong here?
class Person
attr_accessor :fname, :lname, :title
def initialize(fname, lname)
#fname = fname
#lname = lname
#title = title
end
def string1
#lname + ", " + #fname
end
#title = "Director"
def string2
#title
end
end
p = Person.new("Yukihiro", "Matsumoto")
p p.string1
p p.string2
Within the context of your class, #title refers to the class instance variable. Within the context of an instance method, #title refers to an instance variable. You're confusing these two.
What you need instead is a lazy initializer if you have some kind of default that you need applied:
def title
#title ||= "Director"
end
Or better, to populate it in the first place. One way to fix this is to augment your initialize method:
def initialize(fname, lname, title = nil)
#fname = fname
#lname = lname
#title = title || "Director"
end
The following code:
class Person
attr_accessor :fname, :lname
def initialize(fname, lname)
#fname = fname
#lname = lname
end
def string1
#lname + ", " + #fname
end
def title
##title
end
##title = "Director"
def string2
##title
end
end
p = Person.new("Yukihiro", "Matsumoto")
p p.string1
p p.string2
gives the following output when run with Ruby 1.9.3:
"Matsumoto, Yukihiro"
"Director"
I've assumed that you want to keep the title the same for all the objects you create. If not, then iamnotmaynard's answer is what you're after.
You're not passing title to the constructor. You need to have something along the lines of
def initialize(fname, lname, title)
#fname = fname
#lname = lname
#title = title
end
and call it with
p = Person.new("Yukihiro", "Matsumoto", "sensei")
The line #title = "Director" is executed during the class definition, which sets the variable on the class Person itself, rather than during the construction of an instance of the class, which is why the instance variable #title is not being set to "Director" in the new object you create with Person.new.
In fact, here's how to show that this is what's happening. Adding this method
class Person
def self.string3
#title
end
end
adds a class method to Person to get the class's instance variable that was set during the class definition:
>> p Person.string3
"Director"