14

In ruby, I often find myself writing the following:

class Foo
  def initialize(bar, baz)
    @bar = bar
    @baz = baz
  end

  << more stuff >>

end

or even

class Foo
  attr_accessor :bar, :baz

  def initialize(bar, baz)
    @bar = bar
    @baz = baz
  end

  << more stuff >>

end

I'm always keen to minimise boilerplate as much as possible - so is there a more idiomatic way of creating objects in ruby?

grifaton
  • 3,986
  • 4
  • 30
  • 42

6 Answers6

12

One option is that you can inherit your class definition from Struct:

class Foo < Struct.new(:bar, :baz)
  # << more stuff >>
end

f = Foo.new("bar value","baz value")
f.bar #=> "bar value"
f.baz #=> "baz value"
Raimonds Simanovskis
  • 2,948
  • 1
  • 21
  • 17
10

I asked a duplicate question, and suggested my own answer there, expecting for a better one, but a satisfactory one did not appear. I will post my own one.

Define a class method like the following along the spirit of attr_accessor, attr_reader, attr_writer methods.

class Class
    def attr_constructor *vars
        define_method("initialize") do |*vals|
            vars.zip(vals){|var, val| instance_variable_set("@#{var}", val)}
        end
    end
end

Then, you can use it like this:

class Foo
    attr_constructor :foo, :bar, :buz
end

p Foo.new('a', 'b', 'c')      # => #<Foo:0x93f3e4c @foo="a", @bar="b", @buz="c">
p Foo.new('a', 'b', 'c', 'd') # => #<Foo:0x93f3e4d @foo="a", @bar="b", @buz="c">
p Foo.new('a', 'b')           # => #<Foo:0x93f3e4e @foo="a", @bar="b", @buz=nil>
Community
  • 1
  • 1
sawa
  • 165,429
  • 45
  • 277
  • 381
2

You could use Virtus, I don't think it's the idiomatic way to do so but it does all the boiler plate for you.

require 'Virtus'

class Foo
  include 'Virtus'

  attribute :bar, Object 
  attribute :baz, Object

end

Then you can do things like

foo = Foo.new(:bar => "bar") 
foo.bar # => bar

If you don't like to pass an hash to the initializer then add :

def initialize(bar, baz)
  super(:bar => bar, :baz => baz)
end

If you don't think it's DRY enough, you can also do

def initialize(*args)
  super(self.class.attributes.map(&:name).zip(args)])
end
Ross Attrill
  • 2,594
  • 1
  • 22
  • 31
mb14
  • 22,276
  • 7
  • 60
  • 102
2

Struct

Struct object's are classes which do almost what you want. The only difference is, the initialize method has nil as default value for all it's arguments. You use it like this

A= Struct.new(:a, :b, :c)

or

class A < Struc.new(:a, :b, :c)
end

Struct has one big drawback. You can not inherit from another class.

Write your own attribute specifier

You could write your own method to specify attributes

def attributes(*attr)
  self.class_eval do
    attr.each { |a| attr_accessor a }
    class_variable_set(:@@attributes, attr)

      def self.get_attributes
        class_variable_get(:@@attributes)
      end

      def initialize(*vars)
        attr= self.class.get_attributes
        raise ArgumentError unless vars.size == attr.size
        attr.each_with_index { |a, ind| send(:"#{a}=", vars[ind]) }
        super()
      end
  end
end

class A
end

class B < A
  attributes :a, :b, :c
end

Now your class can inherit from other classes. The only drawback here is, you can not get the number of arguments for initialize. This is the same for Struct.

B.method(:initialize).arity # => -1
johannes
  • 7,262
  • 5
  • 38
  • 57
1

I sometimes do

@bar, @baz = bar, baz

Still boilerplate, but it only takes up one line.

I guess you could also do

["bar", "baz"].each do |variable_name|
  instance_variable_set(:"@#{variable_name}", eval(variable_name))
end

(I'm sure there's a less dangerous way to do that, mind you)

https://bugs.ruby-lang.org/issues/5825 is a proposal to make the boilerplate less verbose.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
-1

You could use an object as param.

class Foo
  attr_accessor :param

  def initialize(p)
    @param = p
  end
end

f = Foo.new
f.param.bar = 1
f.param.bax = 2

This does not save much lines in this case but it will if your class has to handle a large number of param. You could also implement a set_param and get_param method if you want to keep your @param var private.

XPac27
  • 301
  • 2
  • 8