Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instance variable access from singleton method

Tags:

ruby

singleton

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
like image 791
fl00r Avatar asked Feb 26 '26 09:02

fl00r


2 Answers

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

like image 148
diedthreetimes Avatar answered Mar 01 '26 00:03

diedthreetimes


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
like image 22
Benoit Garret Avatar answered Feb 28 '26 23:02

Benoit Garret