1

I see this popping up all the time in my code

class Foo
  def initialize(foo)
    @foo = foo
  end
  #...
end

This isn't too bad, but it gets worse:

class Foo
  def initialize(foo,baz,bar,a,b,c,d)
    @foo = foo
    @baz = baz
    @bar = bar
    #etc...

You can sortof get around this by doing something like

@foo, @baz, @bar = foo, baz, bar

But even that feels wrong and is annoying to type. Is there a better way to define instance variables according to arguments?

Edit: There seem to be 2 distinct solutions to this problem. See:

1

6 Answers 6

7

You might want to consider using a Struct:

class Foo < Struct.new(foo,baz,bar,a,b,c,d)
end

foo = Foo.new(1,2,3,4,5,6,7)
foo.bar #=> 2

No need to define an extra initialize method at all...

Sign up to request clarification or add additional context in comments.

Comments

3

Yes, that's the preferred way to initialize instance variables in Ruby. It can be annoying to type, but it's a well understood pattern. As always in Ruby, using metaprogramming to automate it away is possible, but will make your code harder to follow.

I'd also argue that it's probably a good thing for a class to look ugly when it's taking more than two or three arguments. If your class depends on six different things to function, it's a strong candidate for refactoring.

Comments

3
def initialize args
  @foo, @baz, @bar = *args
end

Comments

0

I think there are 3 ways to make initialization of instance variables shorter:

  1. Use Struct or OpenStruct.
  2. Use ruby's parallel assignment.
  3. Use metaprogramming to make a macro like this.

Comments

0

The fattr gem was recently endorsed on Ruby Tapas to help solve this problem. Another consideration though, is whether there are too many things being passed into the initializer. It could be that this class is doing too much and needs to be broken into smaller pieces.

Comments

0

I prefer to follow the following pattern: Dry::Initializer + Memoist/MemoWise (or any similar implementation). It allows me to get rid of the instance variables mentioned in the code completely. That makes the code more robust and readable. For example, this is a real class from one of my projects:

class TransposeKey
  extend Dry::Initializer
  extend Memoist

  option :song_key
  option :capo

  TRANSPOSED_KEYS = [['A'], ['G#', 'Ab'], ['G'], ['Gb', 'F#'], ['F'], ['E'], ['Eb', 'D#'], ['D'], ['Db', 'C#'], ['C'], ['B'], ['A#', 'Bb']]

  SHARP_KEYS = ['A#', 'C#', 'D#', 'F#', 'G#']
  OTHER_KEYS = Parser::Transposer::KEYS.keys - SHARP_KEYS

  def perform
    return capo if song_key == 'numbers'
    transposed_key_index = TRANSPOSED_KEYS.index(TRANSPOSED_KEYS.detect { |i| i.include?(song_key) })
    index = (transposed_key_index + capo) % TRANSPOSED_KEYS.length
    (TRANSPOSED_KEYS[index] & related_keys).join
  end

  private

  memoize def related_keys
    SHARP_KEYS.include?(song_key) ? SHARP_KEYS : OTHER_KEYS
  end
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.