89

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?

ab217
  • 16,900
  • 25
  • 74
  • 92

7 Answers7

91

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

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Steve Weet
  • 28,126
  • 11
  • 70
  • 86
  • Thanks for pointing out the `Person.new(:first_name => "...", :last_name => "...")` method. For some reason it did not cross my mind to use that, but that does answer my question. – ab217 Oct 18 '10 at 12:23
  • 5
    Ruby 2.0 supports named parameters out of the box: http://robots.thoughtbot.com/ruby-2-keyword-arguments – Felipe Nov 18 '14 at 03:42
35

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.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
4
class Person
  def initialize(name, lastName = nil)
    name = name + " " + lastName unless lastName.nil?
    @name = name
  end
end
yfeldblum
  • 65,165
  • 12
  • 129
  • 169
3
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
hexinpeter
  • 1,470
  • 1
  • 16
  • 14
2

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')
snovity
  • 488
  • 4
  • 11
  • This is an underrated answer. It is very helpful for scenarios such as `MyType.parse(foo)` or `MyClass.decode(bar)` using konstructor in addition to the default constructor. – q9f Dec 29 '21 at 14:41
2

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="">
chemturion
  • 283
  • 2
  • 16
1

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
Oshan Wisumperuma
  • 1,808
  • 1
  • 18
  • 32
  • 1
    This is an innovative way to borrow practices from another language. I'd like to see your code example more closely resemble the original poster's language using `Person`, `first_name`, and `last_name`. – chemturion Nov 14 '19 at 21:19