tags:

views:

166

answers:

4

In ruby I want to do roughly the following and have it print out "changed":

class Whatever
    def changeFoo
        @foo="changed"
    end
end
@foo = "original"
o = Whatever.new()
o.changeFoo()
puts "it changed" if @foo == "changed"
puts "it did not change" if @foo == "original"

The problem is, of course, that inside Whatever, @foo belongs to an instance of Whatever.

Is there a way to make changeFoo change the "global" foo? Another way of asking this might be "what object can I reference that "owns" @foo?".

I don't want solutions like "use a global variable" or "use a class variable" or "pass @foo to changeFoo()". I'm specifically asking about the above scenario where I have no control over the original variable nor the way that changeFoo() is called.

I have come up with a workaround where I pass in a reference to the global object at construction time, but I'm not crazy about it since it requires I instantiate Whatever at the proper scope.

class Whatever
    def initialize(main)
        @main = main
    end
    def changeFoo
        @main.instance_variable_set("@foo","changed")
    end
end
o = Whatever.new(self)
+1  A: 

I know this is the answer you don't want, but from your description, you should really should be using a class variable. Also, it's impossible to find the "owner" of a variable because all strings with a certain content (e.g., "original") are indistinguishable - there's no record of where it came from (unless, of course, @foo is really a custom object where you can store a reference to the parent).

Ben Alpert
A: 

Why you would ever want to do something like this is beyond me ... you should not be defining instance variable on your root object and globals are to be avoided at all costs.

This illustrates some ways you can achieve this. You could avoid the object space stuff by simply defining $self = self at the top of your script.

Anyway here is some working sample code, do yourself a favor and don't use it

class Whatever
    def changeFoo
        @foo="changed"
    end

    def changeFoo2(o)
      o.instance_variable_set('@foo', 'changed')
    end

    def changeFoo3
      ObjectSpace.each_object do |o| 
        if o.instance_of? Object and o.instance_variables.include?("@foo")
          o.instance_variable_set('@foo', 'changed')
        end
      end
    end
end
@foo = "original"
o = Whatever.new()
o.changeFoo()
puts "it changed" if @foo == "changed"
puts "it did not change" if @foo == "original"

o.changeFoo2(self) 
puts "it changed" if @foo == "changed"
puts "it did not change" if @foo == "original"

@foo = "original" 

o.changeFoo3() 
puts "it changed" if @foo == "changed"
puts "it did not change" if @foo == "original"
Sam Saffron
The reason is simple: someone else created a DSL using jruby and for whatever reason they chose to store intermediate results of various commands in a variable named @response. Since this variable is being used throughout the dsl both in jruby and java, I'm stuck using it.
Bryan Oakley
A: 

I feel a bit silly doing this, but I'll put it up anyway. Seems like sambo has the right idea. I didn't know about that ObjectSpace module. Sweet.

I wrapped it in a module.

module What

  @foo = 'bar'

  class Whatever
    def change_foo
      What.instance_variable_set(:@foo, "changed")
    end
  end

end


w = What::Whatever.new

p What.instance_variable_get(:@foo)    
# >> "bar"

w.change_foo

p What.instance_variable_get(:@foo)
# >> "changed"
dylanfm
+1  A: 

Late to the party, but you can pass the current context to the method, and then eval the operations on the instance variable in that specific context:

class Whatever
  def changeFoo(context)
    eval %q( @foo="changed" ), context
  end
end
@foo = "original"
o = Whatever.new()
o.changeFoo(binding)
puts "it changed" if @foo == "changed"
puts "it did not change" if @foo == "original"

# => it changed
glenn jackman