Dynamically mixins?

So I'm trying to figure out how I can allow an end user to decide which
modules to mixin to a instance of a class, but I'm not sure if it's
possible. An example of what I would like.

module Thing
  @bar = 5
end

class Foo
  def initialize
    @bar = 0
    @foo = 2
  end
end

class FooBar < Foo
  def initialize(*modules)
    @bar = 1
    modules.each do |module_name|
      eval("include #{module_name.to_s.capitalize}")
    end
  end
end

test = FooBar.new(:thing)

In the previous example, test would be an instance of FooBar, and it's
@bar would be 5 (from the module Thing), and it's @foo would be 2, from
it's superclass. Is that possible? The previous returns a nomethoderror
for "include" in the instance of test. And it might have several other
errors since I just typed it up.

···

--
Posted via http://www.ruby-forum.com/.

module Thing
  def bar
    5
  end
end

class Foo
  attr_reader :bar
  attr_reader :foo
  def initialize
    @bar = 0
    @foo = 2
  end
end

class FooBar < Foo
  def initialize(*modules)
    super()
    @bar = 1
    modules.each do |module_name|
        klass = Kernel.const_get(module_name)
        extend klass
    end
  end
end

test = FooBar.new(:Thing)
puts test.bar
puts test.foo

Not exactly what you are looking for, but close.

···

On Wednesday 29 July 2009 12:34:30 Ch Ba wrote:

So I'm trying to figure out how I can allow an end user to decide which
modules to mixin to a instance of a class, but I'm not sure if it's
possible. An example of what I would like.

module Thing
  @bar = 5
end

class Foo
  def initialize
    @bar = 0
    @foo = 2
  end
end

class FooBar < Foo
  def initialize(*modules)
    @bar = 1
    modules.each do |module_name|
      eval("include #{module_name.to_s.capitalize}")
    end
  end
end

test = FooBar.new(:thing)

In the previous example, test would be an instance of FooBar, and it's
@bar would be 5 (from the module Thing), and it's @foo would be 2, from
it's superclass. Is that possible? The previous returns a nomethoderror
for "include" in the instance of test. And it might have several other
errors since I just typed it up.

Ch Ba wrote:

The previous returns a nomethoderror
for "include" in the instance of test.

Use "extend" rather than "include" if you want to add a module to a
single object, rather than to a class.

I think this does what you want:

module Thing
  def self.extended(obj)
    obj.instance_eval do
      @bar = 5
    end
  end
end

class Foo
  def initialize
    @bar = 0
    @foo = 2
  end
end

class FooBar < Foo
  def initialize(*modules)
    super()
    @bar = 1
    modules.each do |mod|
      extend mod
    end
  end
end

test = FooBar.new(Thing)
p test

If you want to pass a symbol like :thing instead of the actual module
Thing, then you can use Object.const_get as others have pointed out. Or
build a hash:

  ALLOWED_MODULES = {
    :thing => Thing,
  }

  ...
    extend ALLOWED_MODULES[mod]

···

--
Posted via http://www.ruby-forum.com/\.

So I'm trying to figure out how I can allow an end user to decide which
modules to mixin to a instance of a class, but I'm not sure if it's
possible. An example of what I would like.

module Thing
  @bar = 5
end

This does not work as it defines a member of module Thing with value 5. You would rather need something like this:

module Thing
   attr_writer :bar

   def bar
     @bar ||= 5
   end
end

But this does not solve the initialization problem. For that you need a bit more complex logic (see below).

class Foo
  def initialize
    @bar = 0
    @foo = 2
  end
end

class FooBar < Foo
  def initialize(*modules)
    @bar = 1
    modules.each do |module_name|
      eval("include #{module_name.to_s.capitalize}")
    end
  end
end

test = FooBar.new(:thing)

In the previous example, test would be an instance of FooBar, and it's
@bar would be 5 (from the module Thing), and it's @foo would be 2, from
it's superclass. Is that possible? The previous returns a nomethoderror
for "include" in the instance of test. And it might have several other
errors since I just typed it up.

module Thing
  def bar
    5
  end
end

class Foo
  attr_reader :bar
  attr_reader :foo
  def initialize
    @bar = 0
    @foo = 2
  end
end

class FooBar < Foo
  def initialize(*modules)
    super()
    @bar = 1
    modules.each do |module_name|
        klass = Kernel.const_get(module_name)
        extend klass
    end
  end
end

test = FooBar.new(:Thing)
puts test.bar
puts test.foo

Not exactly what you are looking for, but close.

I believe what OP wants can be achieved doing something like this (untested):

module Thing
   attr_acessor :bar

   def initialize
     # deliberately no super here
     self.bar = 5
   end
end

class FooBar
   def initialize(*modules)
     super()
     @bar = 1

     modules.each do |mod|
       extend mod

       begin
         mod.instance_method(:initialize).bind(self).call()
       rescue NameError
         # ignore
       end
     end
   end
end

test = FooBar.new(Thing)
puts test.bar
puts test.foo

Kind regards

  robert

···

On 30.07.2009 00:16, spox wrote:

On Wednesday 29 July 2009 12:34:30 Ch Ba wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Ch Ba wrote:

The previous returns a nomethoderror
for "include" in the instance of test.

Use "extend" rather than "include" if you want to add a module to a single object, rather than to a class.

I think this does what you want:

module Thing
  def self.extended(obj)
    obj.instance_eval do
      @bar = 5
    end
  end
end

That's a nice idea to use #extended!

class Foo
  def initialize
    @bar = 0
    @foo = 2
  end
end

class FooBar < Foo
  def initialize(*modules)
    super()
    @bar = 1
    modules.each do |mod|
      extend mod
    end
  end
end

test = FooBar.new(Thing)
p test

If you want to pass a symbol like :thing instead of the actual module Thing, then you can use Object.const_get as others have pointed out. Or build a hash:

  ALLOWED_MODULES = {
    :thing => Thing,
  }

  ...
    extend ALLOWED_MODULES[mod]

May I suggest to use #fetch in that case - the error might be a bit more telling.

Kind regards

  robert

···

On 30.07.2009 16:21, Brian Candler wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Robert Klemme wrote:

  ALLOWED_MODULES = {
    :thing => Thing,
  }

  ...
    extend ALLOWED_MODULES[mod]

May I suggest to use #fetch in that case - the error might be a bit more
telling.

Yes, that's a good idea. I'm in the habit of writing things like

  extend(ALLOWED_MODULES[mod] || (raise "Unknown module
#{mod.inspect}"))

but I thought that might clutter the sample code in this case. I always
forget about Hash#fetch.

Another way is:

  ALLOWED_MODULES = Hash.new { raise "Oi!" }
  ALLOWED_MODULES.merge!({
    .. etc
  })

···

--
Posted via http://www.ruby-forum.com/\.