In ruby, I often find myself writing the following:
class Foo
def initialize(bar, baz)
@bar = bar
@baz = baz
end
<< more stuff >>
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>
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"
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.
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
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.
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
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.