3

How can I access instance variable from singleton method?

class Test
  def initialize(a)
    @a = a
  end

  def item
    item = "hola"
    def item.singl
      [self, @a].join(" + ")
    end
    item
  end
end

test = Test.new("cao")
item = test.item
item.singl
#=> ... @a is nil
0

3 Answers 3

6

Try using define_method. Def puts you inside a new scope.

class Test
  def initialize(a)
    @a = a
  end

  def item
    item = "hola"
    item.singleton_class.send(:define_method, :singl) do
      [self, @a].join(" + ")
    end

    item
  end
end

test = Test.new("cao")
item = test.item
item.singl #=> "hola + "

In your example though, you still have a problem, inside the singleton class of a string @a hasn't been defined. This is primarily because self in this context is the string instance, not a test instance where @a exists. To fix this you can rebind the instance variable to something else, but this might not be the behavior you're looking for. You can also, set the instance variable in your new singleton class.

For example,

Rebind the variable

class Test
  def initialize(a)
    @a = a
  end

  def item
    item = "hola"
    new_self = self
    item.singleton_class.send(:define_method, :singl) do
      [self, new_self.instance_variable_get(:@a)].join(" + ")
    end

    item
  end
end

test = Test.new("cao")
item = test.item
item.singl

Set a instance string variable

class Test
  def initialize(a)
    @a = a
  end

  def item
    item = "hola"
    item.singleton_class.send(:define_method, :singl) do
      [self, @a].join(" + ")
    end

    item.singleton_class.send(:define_method, :set) do
      @a = "cao"
    end

    item
  end
end

test = Test.new("cao")
item = test.item
item.set
item.singl

Its important to note the differences between the two methods. In the first method, we retain a reference to the original instance variable, via the original object. In the second method, we make a new instance variable, bound under the new singleton class, containing a copy of the original test.@a.

If you are using a non-native object you may be able to get away with a mixture of both methods. Referencing the old instance variable's object with the singelton classes new instance variable via a pointer, but this won't work for int, string, float, etc...

EDIT: As Benoit pointed out, in the second method the "set" method should just be an attr_accessor. In fact, you can set the instance variable without defining a new method at all. Via item.instance_variable_set(:@, "cao")

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

2 Comments

I'm having trouble understanding your last example, you're hardcoding the "cao" string in the item class instead of getting what was passed to the Test initializer. Did you mean to define it as an attribute setter?
I got lazy. I made this distinction to illustrate the fact that with the second method, the two @a's are not the same. I assumed the op could infer that this method "should" be an attribute setter. I'll clarify. Thanks!
1

You're trying to set your instance variable on the Test class and retrieve it in the string instance, these are not the same objects and do not share instance variables. You could do the following to pass it between the two instances:

class Test
  def initialize(a)
    @a = a
  end

  def item
    item = "hola"
    item.singleton_class.send :attr_accessor, :a
    # set the item's @a with the Test instance one
    item.a = @a
    def item.singl
      [self, @a].join(" + ")
    end
    item
  end
end

test = Test.new("cao")
item = test.item
puts item.singl

2 Comments

In your example you have a minor problem. The new item references a copy of @a and not @a itself. This is a probelem if test.a ever changes. For instance this will cause an incorect result. item.singl; test.a = "hi"; item.singl. Of course this is really only true for strings...
Thanks for the warning, I won't touch my answer though. Yours is already much better than mine ;-)
1

You are not trying to access an instance variable of item. item is a String object whereas @a is an instance variable of the Test object test.

Both are independent. The only way to access that @a from item is to have a reference to test (or @a) in item, e.g.

class Test   
  attr_reader :a
  def initialize(a)     
    @a = a   
  end    

  def item     
    item = "hola" 
    def item.singl       
      [self, @parent.a].join(" + ")     
    end 
    item.instance_variable_set(:@parent, self)

    item    
  end 
end  

test = Test.new("cao")
item = test.item 
item.singl

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.