Question

I find my self repeating things a lot when I create classes in ruby, often I will end up with something similar to the following:

class Foo
  attr_reader :bar_0,
              :bar_1,
                 .
                 .
                 .
              :bar_n
  def initialize( bar_0 = something,
                  bar_1 = something,
                        .
                        .
                        .
                  bar_n = something)
    @bar_0 = bar_0
    @bar_1 = bar_1
           .
           .
           .
    @bar_n = bar_n
  end
end

Does ruby employ a shortcut for more efficiently implementing something like this?

Was it helpful?

Solution

Judging from the way the question is phrased, you should probably rethink the design of your classes. However, Ruby provides an interesting way to quickly create classes with attr_accessors (not readers). Here's a simple example:

>> class Person < Struct.new(:name, :age) ; end
=> nil
>> p = Person.new
=> #<struct Person name=nil, age=nil>
>> p.age = 23
=> 23
>> p.class
=> Person
>> p.methods.grep(/age/)
=> [:age, :age=]

Of course this is a normal class and you can add all the methods you want (and use getters and setters instead of instance variables, e.g. var for the getter and self.var = foo for the setter).

If you really don't want the writers, make them private or undef them.

>> attrs = [:name, :age]
=> [:name, :age]
>> class Person < Struct.new *attrs ; end
=> nil
>> Person.instance_eval { private *attrs.map{|attr| "#{attr}=" }}
=> Person
>> p = Person.new
=> #<struct Person name=nil, age=nil>
>> p.methods.grep(/age/)
=> [:age]

All of the above doesn't help with the tons of assignments in initialize of course, but then one wonders if you really want to many constructor arguments or if maybe you just have one hash argument and merge that into a default hash.

OTHER TIPS

Ruby is dynamic and offers a lot in terms of introspection, so you can make use of metaprogramming (or writing code that essentially writes code). In your example there are several things you can do to clean up the verbosity:

class Foo
  # Rather than writing bar_1, bar_2, bar_3, ...
  attr_accessor ((0..9).to_a + ('a'..'n').to_a).map { |x| :"foo_#{x}" }

  # Using mass assignment...
  def initialize(attributes = {})
    attributes.each do |attribute, value|
      respond_to?(:"#{attribute}=") && send(:"#{attribute}=", value)
    end
  end
end

Since mass assignment is a popular and reusable behavior, it makes sense to extract it into a separate module and make it a mixin:

module MassAssignment
  def initialize(attributes = {})
    mass_assign(attributes)
  end

  def mass_assign(attributes)
    attributes.each do |attribute, value|
      respond_to?(:"#{attribute}=") && send(:"#{attribute}=", value)
    end
  end
end

class Foo
  include MassAssignment
end

You can use introduce parameter object refactoring method to simplify constructor call.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top