如何从动态包含的模块中访问实例变量?

时间:2014-07-11 16:38:09

标签: ruby

我动态地将一个模块包含在Baz方法的foobarbaz类中。

然而,当我在ruby中执行此操作时,我得到一个nil puts。模块是否可以访问Foo的实例变量?

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

注意,为此,我使用的是Ruby 2.0.0。以下内容在Ruby 2.1.2中也没有puts期望的结果。

3 个答案:

答案 0 :(得分:2)

以下是元编程

#!/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

  

在mod的上下文中计算字符串或块,除了在给出块时,常量/类变量查找不受影响....

因此在Bar.module_eval { @current_session = session }内,@current_session只是Bar的实例变量,我将它的值设置为类Foo的实例变量值,是@current_session

Baz.send(:include, Bar)是有帮助的,它返回类/模块本身,包括另一个模块。 include(module, ...) → self

class Baz
end

阅读此post以了解以下内容。

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

<强>更新

由于@Christian Fazzin给出了一个很好的建议: -

如果您希望Bar模块也有写入方法,那么您必须进行2次更改 -

  • Bar应该包含attr_accesor :current_session,而不是它现在拥有的内容。
  • 你不需要在那里使用module_eval的强大功能,而是使用写法的语法sugraness ,比如把Bar.current_session = current_session放在方法{{1}中}。删除行foobarbazsession = current_session

答案 1 :(得分:1)

创建Foofoo之后的实例)之后,将foo的实例变量@current_session初始化为'current session',它会显示对我来说,你希望foo.foobarbaz做以下事情:

  • 导致Bazinclude模块Bar
  • 创建Bazbaz,比如说)
  • 的实例
  • @current_session创建一个名为baz的实例变量,并为其指定foo同名实例变量的值
  • 调用baz.foobar以返回baz的实例变量@current_session的值。

如果我的理解是正确的,我们可以在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"

我稍微修改了foobarbaz返回的内容以显示其来源。

请注意,foobarbaz的第三行可以更改为以下任一项

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

如果使用后者,则不需要@current_session的访问者。

答案 2 :(得分:0)

您只需要设置类@current_session的实例变量(不是类实例变量!)Baz

对代码进行最轻微的修改而不需要额外的类/模块方法,最直接的方法是定义设置所需变量的初始化方法:

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