Christian Fazzini
Christian Fazzini

Reputation: 19713

How do I access an instance variable from a module that was included dynamically?

I am dynamically including a module into the Baz class in the foobarbaz method.

However, when I execute this in ruby, I get a nil puts. Doesn't the module have access to Foo's instance variables?

class Foo
  attr_accessor :current_session

  def initialize(current_session)
    @current_session = current_session
  end

  def foobarbaz
    Baz.send(:include, Bar) # For Ruby < 2.1
    # Baz.include(Bar) # For Ruby > 2.1
  end
end

class Baz
end

module Bar
  def foobar
    @current_session
    # 'foobar'
  end
end

puts Foo.new('current_session').foobarbaz.new.foobar # nil

NOTE, for this, I was using Ruby 2.0.0. The following also does not puts desired result in Ruby 2.1.2.

Upvotes: 0

Views: 547

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110665

After creating an instance of Foo (foo, say) and in doing so initializing foo's instance variable @current_session to 'current session', it appears to me that you want foo.foobarbaz to do the following:

  • cause Baz to include the module Bar
  • create an instance of Baz (baz, say)
  • create an instance variable named @current_session for baz and assign it the value of foo's instance variable of the same name
  • invoke baz.foobar to return the value of baz's instance variable @current_session.

If my understanding is correct, we can perform these four steps with four lines in Foo#foobarbaz:

class Baz
end

module Bar
  def foobar
    @current_session + ' in Baz'
  end
end

class Foo
  attr_accessor :current_session

  def initialize(current_session)
    @current_session = current_session
  end

  def foobarbaz
    Baz.include(Bar)
    baz = Baz.new
    baz.instance_variable_set(:@current_session, self.current_session)
    baz.foobar  
  end
end

foo = Foo.new('current session')
foo.foobarbaz
  #=> "current session in Baz"

I've slightly modified what foobarbaz returns to show where it is coming from.

Note that the third line of foobarbaz could be changed to either of the following

baz.instance_variable_set(:@current_session, @current_session)
baz.instance_variable_set(:@current_session,
      instance_variable_get(:@current_session))

If the latter of these were used, @current_session's accessor would not be needed.

Upvotes: 1

Arup Rakshit
Arup Rakshit

Reputation: 118261

Here is a meta programming for you :

#!/usr/bin/env ruby

class Foo
  attr_accessor :current_session

  def initialize(current_session)
    @current_session = current_session
  end

  def foobarbaz
    session = current_session
    Bar.module_eval { @current_session = session }
    Baz.send(:include, Bar)
  end
end

module_eval says

Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected....

Thus inside Bar.module_eval { @current_session = session }, @current_session is the instance variable of Bar only and I am setting the value of it to the instance variable value of the class Foo, which is @current_session.

Baz.send(:include, Bar) is helpfull, which returns class/module itself, which is including the other module. include(module, ...) → self.

class Baz
end

Read this post to understand the below stuff.

module Bar
  class << self
    attr_reader :current_session
  end

  def foobar
    Bar.current_session
  end
end

puts Foo.new('current_session').foobarbaz.new.foobar
# >> current_session

Update

As @Christian Fazzin gave a good suggestion :-

If you want Bar module to have write method also, then you have to do 2 changes -

  • Bar should contain then attr_accesor :current_session, instead of what it has now.
  • You don't need to use the power of module_eval there, rather use syntactic sugraness of write methods, like put Bar.current_session = current_session inside the method foobarbaz . Remove the lines session = current_session and Bar.module_eval { @current_session = session }.

Upvotes: 2

David Unric
David Unric

Reputation: 7719

You'd just need to set instance variable (not class instance variable!) @current_session of class Baz.

With slightest modification of your code without need of additional class/module methods the most straightforward way is to define initialization method that sets the required variable:

class Foo
  attr_accessor :current_session

  def initialize(current_session)
    @current_session = current_session
  end

  def foobarbaz
    # define Baz#initialize on-the-fly, alternatively with define_method
    Baz.class_eval "def initialize; @current_session = '#{@current_session}';end"
    Baz.send(:include, Bar) # For Ruby < 2.1
  end
end

class Baz
end

module Bar
  def foobar
    @current_session
    # 'foobar'
  end
end

puts Foo.new('current_session').foobarbaz.new.foobar
# current_session

Upvotes: 0

Related Questions