6

Is there an easy way to bulk assign instance variables

  def initialize(title: nil, label_left: nil, label_right: nil, color_set: nil)
    @title = title
    @label_left = label_left
    @label_right = label_right
    @color_set = color_set
  end

Can I loop/iterate through the initialize arguments and assign automatically?

2 Answers 2

11

If you want specific variables, then not really. Using reflection here, even if it was available, would be trouble.

What you've got here is the most trivial case. Often you'll see code that looks more like this:

def initialize(title: nil, label: nil, label_width: nil, color_set: nil)
  @title = title.to_s
  @label = label_left.to_s
  @label_width = label_width.to_i
  @color_set = MyRGBConverter.to_rgb(color_set)
end

The initializer is a place where you can do any necessary conversion and validation. If some of those arguments are required to be certain values you'll have tests for that, raise an exception on an error and so forth. The repetitive code you have here in the minimal case often gets expanded and augmented on so there's no general purpose solution possible.

That leads to code like this:

def initialize(title: nil, label: nil, label_width: nil, color_set: nil)
  @title = title.to_s
  unless (@title.match(/\S/))
    raise "Title not specified"
  end

  @label = label_left.to_s
  unless (@label.match(/\A\w+\z/))
    raise "Invalid label #{@label.inspect}"
  end

  @label_width = label_width.to_i
  if (@label_width <= 0)
    raise "Label width must be > 0, #{@label_width} specified."
  end

  @color_set = MyRGBConverter.to_rgb(color_set)
end

If you really don't care about which arguments you're taking in then you can do this in Ruby 2.3 with the new keyword-arguments specifier **:

def initialize(**options)
  options.each do |key, value|
    instance_variable_set("@#{key}", value)
  end
end

Note that's potentially dangerous if those values are user supplied, so you might want to white-list them somehow:

VALID_OPTIONS = [ :title, :label_left, :label_right, :color_set ]

def initialize(**options)
  options.each do |key, value|
    raise "Unknown option #{key.inspect}" unless (VALID_OPTIONS.include?(key))

    instance_variable_set("@#{key}", value)
  end
end

Where you're seeing a lot of these arguments being passed in on a regular basis you might want to evaluate if creating some kind of struct-like object and passing that through might be a better plan. It depends on how your code works.

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

Comments

2

@tadman provided already an excellent answer to this, but here is one more: If you are willing to dispense with named parameters, you could do it like this:

def initialize(*args)
  @title, @label_left, @label_right, @color_set, *nada = args
  fail "Too many arguments" unless nada.empty?
end 

[UPDATE: Fixed the code, according to the comment given by @sawa].

4 Comments

You need to change it to *nada, unless nada.empty? to make it work as you intended. Rubyists prefer to write simply as *.
Thanks for pointing out the error. I will fix it in my answer. Could you elaborate on the second part of your comment? How could you use a sole * in this case?
Sorry, you need *nada just like you did. By the way, there is no empty method.
@sawa: You are as a careful reader as I am a sloppy coder. Fixed.

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.