32

I would like to have a class and some attributes which you can either set during initialization or use its default value.

class Fruit
  attr_accessor :color, :type
  def initialize(color, type)
    @color=color ||= 'green'
    @type=type ||='pear'
  end
end

apple=Fruit.new(red, apple)
Istvan
  • 7,500
  • 9
  • 59
  • 109

8 Answers8

71

The typical way to solve this problem is with a hash that has a default value. Ruby has a nice syntax for passing hash values, if the hash is the last parameter to a method.

class Fruit
  attr_accessor :color, :type

  def initialize(params = {})
    @color = params.fetch(:color, 'green')
    @type = params.fetch(:type, 'pear')
  end

  def to_s
    "#{color} #{type}"
  end
end

puts(Fruit.new)                                    # prints: green pear
puts(Fruit.new(:color => 'red', :type => 'grape')) # prints: red grape
puts(Fruit.new(:type => 'pomegranate')) # prints: green pomegranate

A good overview is here: http://deepfall.blogspot.com/2008/08/named-parameters-in-ruby.html

Roman Zabicki
  • 214
  • 2
  • 4
  • 14
Brian Clapper
  • 25,705
  • 7
  • 65
  • 65
  • 1
    I like this solution the most among the others. The reason is we won't need to create any further significant objects (imagine a big class instance for example) in case the params already has those options. But I also would like to use even cleaner syntax (just my opinion) like this: @color = params[:color] || 'green' – Hoang Le Jul 02 '15 at 03:14
  • I'm going to have an opportunity to do a little ruby/jruby! After 7 years! – WestCoastProjects Jan 16 '20 at 01:39
25

Since Ruby 2.0 there is support of named or keyword parameters.

You may use:

class Fruit
  attr_reader      :color, :type

  def initialize(color: 'green', type: 'pear')
    @color = color
    @type = type
  end

  def to_s
    "#{color} #{type}"
  end
end

puts(Fruit.new)                                    # prints: green pear
puts(Fruit.new(:color => 'red', :type => 'grape')) # prints: red grape
puts(Fruit.new(:type => 'pomegranate')) # prints: green pomegranate

Some interesting notes on this topic:

Community
  • 1
  • 1
knut
  • 27,320
  • 6
  • 84
  • 112
12

Brian's answer is excellent but I would like to suggest some modifications to make it mostly meta:

class Fruit

  # Now this is the only thing you have to touch when adding defaults or properties
  def set_defaults
    @color ||= 'green'
    @type  ||= 'pear'
  end

  def initialize(params = {})
    params.each { |key,value| instance_variable_set("@#{key}", value) }
    set_defaults
    instance_variables.each {|var| self.class.send(:attr_accessor, var.to_s.delete('@'))}
  end

  def to_s
    instance_variables.inject("") {|vars, var| vars += "#{var}: #{instance_variable_get(var)}; "}
  end

end

puts Fruit.new
puts Fruit.new :color => 'red', :type => 'grape'  
puts Fruit.new :type => 'pomegranate'
puts Fruit.new :cost => 20.21
puts Fruit.new :foo => "bar"


f = Fruit.new :potato => "salad"
puts "f.cost.nil? #{f.cost.nil?}"

Which outputs:

@color: green; @type: pear; 
@color: red; @type: grape; 
@color: green; @type: pomegranate; 
@color: green; @type: pear; @cost: 20.21; 
@color: green; @type: pear; @foo: bar; 
f.cost.nil? true

Of course this wouldn't be a perfect solution for everything but it gives you some ideas on making your code more dynamic.

6

I like vonconrad's answer but would have a separate defaults method. Maybe it's not efficient in terms of lines of code, but it's more intention-revealing and involves less cognitive overhead, and less cognitive overhead means more efficient dev onboarding.

class Fruit
  attr_accessor :color, :type

  def initialize(args={})
    options = defaults.merge(args)

    @color = options.fetch(:color)
    @type  = options.fetch(:type)
  end

  def defaults
    {
      color: 'green',
      type:  'pear'
    }
  end
end

apple = Fruit.new(:color => 'red', :type => 'apple')
aceofbassgreg
  • 3,837
  • 2
  • 32
  • 42
6

I'd do it like this:

class Fruit
  attr_accessor :color, :type

  def initialize(args={})
    options = {:color => 'green', :type => 'pear'}.merge(args)

    self.color = options[:color]
    self.type  = options[:type]
  end
end

apple = Fruit.new(:color => 'red', :type => 'apple')

This way, you never have to worry about missing arguments--or their order--and you'll always have your default values right there. .merge will of course overwrite the default values if they're present.

vonconrad
  • 25,227
  • 7
  • 68
  • 69
4

More simple way:

class Fruit
  attr_accessor :color, :type
  def initialize(color = 'green', type = 'pear')
    @color = color
    @type = type
  end
  def to_s
    "#{color} #{type}"
  end
end


puts Fruit.new # prints: green pear
puts Fruit.new('red','apple') # prints: red apple
puts Fruit.new(nil,'pomegranate') # prints: green pomegranate
Amer
  • 1,968
  • 4
  • 21
  • 25
  • 2
    Yes, except this approach requires that you pass the parameters in order. With the hash approach, you must specify the parameter name (i.e., the hash key), but you can pass those keys in any order. – Brian Clapper Dec 29 '10 at 16:35
2

Even more tasty syntactic sugar:

class Fruit
  attr_accessor :color, :type
  def initialize *args
    @color, @type = args 
  end
end

pear = Fruit.new 'green', :pear
jasonleonhard
  • 12,047
  • 89
  • 66
0

Ruby version: 3.1

I faced the same issue while trying to initialize my classes with some default values.

The best approach for me was to use "named argument". Check out this link for a better understanding.

class Fruit
  attr_accessor :color, :type
  def initialize(color: 'green', type: 'pear')
    @color = color
    @type = type
  end
end

apple=Fruit.new(color: red, type: apple)
kosher
  • 141
  • 2
  • 5