mbond
mbond

Reputation: 47

Dynamic Variables to access class methods in Ruby

Working in Ruby, we have to use a 3rd party Framework, which has a class setup something like this:

class Foo 

    attr_accessor :bar

    def initialize() 
    end

end

class Poorly_Designed_Class

    attr_accessor :thing1
    attr_accessor :thing2
    attr_accessor :thing3
    attr_accessor :thing4
    attr_accessor :thing5

    # through :thing_n .. number defined at runtime

    def initialize()
        @thing1 = Foo.new
        @thing2 = Foo.new
        @thing3 = Foo.new
        @thing4 = Foo.new
        @thing5 = Foo.new
    end

end

I don't know how many "things" there are until run time. there could be 5 or their could be 50.

What I would like to do is something like:

pdc = Poorly_Designed_Class.new
for i in 0..numberOfThings do
    pdc."thing#{i}".bar = value[i]
end

The above doesn't work.

I've also tried accessing it via:

instance_variable_set("pdc.thing#{i}.bar",value)

I understand that the class should be using an array or hash. Unfortunately I can't do anything about how the class is designed and we have to use it.

Is what i'm trying to do even possible?

Upvotes: 0

Views: 479

Answers (2)

cremno
cremno

Reputation: 4927

You're using Object#instance_variable_set incorrectly. The first argument must be a string or a symbol representing the name of an instance variable including the @ prefix: e.g. "@thing{i}". However you actually want to get the value of an instance variable and then send #bar= to it. That can be done with Object#instance_variable_get:

1.upto(numberOfThings) { |i| pdc.instance_variable_get("@thing#{i}").bar = value[i] }

That's a bit long and since attr_acessor :thingX defines getter methods, it's usually preferable to call them with Object#public_send instead of directly accessing the instance variable (a getter method might do something else than just returning a value):

1.upto(numberOfThings) { |i| pdc.public_send("thing#{i}").bar = value[i] }

Upvotes: 0

Jörg W Mittag
Jörg W Mittag

Reputation: 369428

You could either try to call the getter (preferably, since it honors encapsulation):

pdc = PoorlyDesignedClass.new
1.upto(number_of_things.times do |i|
  pdc.public_send(:"thing#{i}").bar = value[i]
end

or get the instance variable (less preferred, since it breaks encapsulation):

pdc = PoorlyDesignedClass.new
1.upto(number_of_things) do |i|
  pdc.instance_variable_get(:"@thing#{i}").bar = value[i]
end

So, you were on the right track, there were just two problems with your code: instance variable names start with an @ sign, and . is not a legal character in an identifier.

Upvotes: 1

Related Questions