Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby dynamic module mix-in

Tags:

ruby

mixins

Suppose I have two modules:

module Test1

  attr_accessor :a, :b

  @a = 0.0
  @b = 0.0

end

module Test2

  attr_accessor :c, :d

  @c = 0.0
  @d = 0.0

end

Now, I want to conditionally mix these modules into a class. This is what I've tried:

require './Test1.rb'
require './Test2.rb'

class MyClass

  def initialize(mode)
    if mode == 0
      (class << self; include Test1; end)
    elsif mode == 1
      (class << self; include Test2; end)
    else
      class << self
        include Test1
        include Test2
      end
    end
  end

end

This is the behavior I am seeing:

obj = MyClass.new(0)
obj.a  #=> nil

Also @a is nil in instance methods within the class. I feel that I am not understanding something important here. I would like to understand why what I'm doing isn't working and also what the correct way to achieve my desired functionality is.

like image 957
andrew.cuthbert Avatar asked Jul 03 '13 14:07

andrew.cuthbert


2 Answers

You have this behaviour because these instance variables you set in modules belong to modules themselves instead of belonging to MyClass instances. Consider this code:

Test1.instance_variable_get(:@a)
# => 0.0

To solve this issue, you could use extend instead of include:

module Test1

  attr_accessor :a, :b

  def self.extended(object)
    object.a, object.b = 0.0, 0.0
  end

end

module Test2

  attr_accessor :c, :d

  def self.extended(object)
    object.c, object.d = 0.0, 0.0
  end

end

And in your class:

require './Test1.rb'
require './Test2.rb'

class MyClass

  def initialize(mode)
    if mode == 0
      extend Test1
    elsif mode == 1
      extend Test2
    else
      extend Test1
      extend Test2
    end
  end

end
like image 51
Marek Lipka Avatar answered Nov 15 '22 06:11

Marek Lipka


I thought of a way to work around this problem, so I thought I'd share it. I'd still like to see if anyone knows of a better way to achieve what I was trying to.

Module Test1
  attr_accessor :a, :b

  def init1
    @a = 0.0
    @b = 0.0
  end
end

class MyClass
  def initialize
    if mode == 0
      (class << self; include Test1; end)
      init1
    elsif mode == 1
      ...
  end
end
like image 22
andrew.cuthbert Avatar answered Nov 15 '22 06:11

andrew.cuthbert