fl00r
fl00r

Reputation: 83680

Instance variable access from singleton method

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

Upvotes: 3

Views: 1587

Answers (3)

undur_gongor
undur_gongor

Reputation: 15954

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

Upvotes: 1

diedthreetimes
diedthreetimes

Reputation: 4115

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")

Upvotes: 6

Benoit Garret
Benoit Garret

Reputation: 13675

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

Upvotes: 1

Related Questions