22

I am using Ruby on Rails 3.0.9 and I am trying to set "dynamically" some variable values. That is...

... in my model file I have:

attr_accessor :variable1, :variable2, :variable3


# The 'attributes' argument contains one or more symbols which name is equal to 
# one or more of the 'attr_accessor' symbols.

def set_variables(*attributes)

  # Here I should set to 'true' all ":variable<N>" attributes passed as symbol
  # in the 'attributes' array, but variable names should be interpolated in a 
  # string.
  # 
  # For example, I should set something like "prefix_#{':variable1'.to_s}_suffix".

end

How can I set those variable values to true?


I tried to use the self.send(...) method, but I did not succeed (but, probably, I don't know how to use at all that send method... is it possible do to that I need by using the send method?!).

5 Answers 5

65
attr_accessor :variable1, :variable2, :variable3

def set_variables(*attributes)
  attributes.each {|attribute| self.send("#{attribute}=", true)}
end
Sign up to request clarification or add additional context in comments.

Comments

9

Here's the benchmark comparison of send vs instance_variable_set:

require 'benchmark'

class Test
  VAR_NAME = '@foo'
  ATTR_NAME = :foo

  attr_accessor ATTR_NAME

  def set_by_send i
    send("#{ATTR_NAME}=", i)
  end

  def set_by_instance_variable_set i
    instance_variable_set(VAR_NAME, i)
  end
end

test = Test.new

Benchmark.bm do |x|
  x.report('send                 ') do
    1_000_000.times do |i|
      test.set_by_send i
    end
  end
  x.report('instance_variable_set') do
    1_000_000.times do |i|
      test.set_by_instance_variable_set i
    end
  end
end

And the timings are:

      user     system      total        real
send                   1.000000   0.020000   1.020000 (  1.025247)
instance_variable_set  0.370000   0.000000   0.370000 (  0.377150)

(measured using 1.9.2)

It should be noted that only in certain situations (like this one, with accessor defined using attr_accessor) are send and instance_variable_set functionally equivalent. If there is some logic in the accessor involved, there would be a difference, and you would have to decide which variant you would need of the two. instance_variable_set just sets the ivar, while send actually executes the accessor method, whatever it does.

Another remark - the two methods behave differently in another aspect: if you instance_variable_set an ivar which doesn't exist yet, it will be created. If you call an accessor which doesn't exist using send, an exception would be raised.

5 Comments

You're comparing apples and oranges. As I explained in another comment, the attribute names are coming in without leading @, so you'd need to compare send("#{ATTR_NAME}=", i) with instance_variable_set("@#{ATTR_NAME}", i) to replicate the OP's actual use case. How do those numbers look?
I didn't forget. Now, how about an answer to my question?
Your first comment on skorks' answer was that you think send was better alternative, and then downvoted it "for messing with internals". For god sake, it is an instance method, its job is to "mess with internals", i.e. change the internal state of the object. Both ways do the job, they change the ivars, so it's OK to prefer one or the other, but it's NOT ok to downvote. So please revert the downvote and then we can continue.
You make a good point about the job of an instance method being to mess with internals. However, I think it's reprehensible for you to hold this discussion hostage to my changing my vote. I'll change my vote if I am convinced to do so by good argumentation; I will not change it solely in order to continue a discussion. That would hurt the integrity of the vote system.
There's no need arguing NOT downvoting. You would need to provide a credible argument for downvoting. And OP's question is broad enough to allow for more than a single possible solution. We could argue which would be better or more suitable for the particular need. But none of the given answers is neither wrong nor misleading.
4

The method you're after is instance_variable_set so in your case:

def set_variables(*attributes)
  attributes.each {|attribute| self.instance_variable_set(attribute, true)}
end

8 Comments

I think send is the better alternative here.
Why? String interpolation is plain ugly there. Also should be slower as you have additional method calls.
It won't be any slower, I think. And instance_variable_set is messing too much with the object's internals, whereas send works through the interface. instance_variable_set is dangerous and rarely a good idea.
The more I think about, the more I think that this merits a downvote for messing with internals.
@MladenJablanović Rereading your original comment. The idea that there are "additional method calls" is incorrect -- every send call in my way of doing it corresponds to an instance_variable_set call here.
|
3
def set_attributes(*attributes)
  attributes.each do |attr|
    self.send "#{attr}=", true
  end
end

Remember that setter method names end with = in Ruby.

Comments

1

I know the question was for Rails 3, but the question came up when searching for Rails 4 answers about "how to dynamically access variable values". I tested this on my model and it worked great as an alternative to the proposed solutions:

def set_variables(*attributes)
  attributes.each {|attribute| self["#{attribute}"] = true}
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.