views:

102

answers:

3

Hi guys,

I'm trying to make a Ruby class that is very flexible as to its type and hopefully can inherit properties from a number of other classes depending on the values its initialized with:

class Test
  def initialize(type,etc)
    case type
    when "stringio"
      inherit_from_stringio_with_data(etc)
    when "list"
      inherit_from_enumerable_with_data(etc)
    # and so on
    end
  end
end

Test.new("list").each do |item|
  p item
end

s = Test.new("stringio")
s.seek(3)
puts s.read(2)

I know of - or rather have read of - the power of the mixin, but as far as I can tell this isn't quite laid out correctly. Does anyone have any ideas, or am I trying something that's best achieved otherways (by having, say @content, that contains etc as a StringIO, Enumerable etc).

Thanks!

+2  A: 

There's no reason to pass a string representing a class name — you can just pass a class or instance of a class. Here's a version that takes an object and switches on its type.

class Test
  def initialize(obj)
    @content = obj
    forwarded_methods = case @content
      when StringIO then [:seek, :read]
      when Enumerable then [:each]
      else raise "Invalid type"
    end
    eigenclass = class<<self; self; end
    eigenclass.class_eval do
      extend Forwardable
      def_delegators :@content, *forwarded_methods
    end
  end
end

Test.new([1,2,3]).each do |item|
  p item
end

s = Test.new(some_stringio_object)
s.seek(3)
puts s.read(2)
Chuck
I'm actually parsing a byte string to generate these objects so I have to create the objects on-the-fly but this forwarding of methods seems perfect!Just a thought, is there an easy way to forward all methods, or exclude certain methods, rather than having to specify them all?
JP
There's no need to explicitly list all of them. You could easily set `forwarded_methods` to be something like `@content.methods - Object.instance_methods - [some list of other methods to exclude]`.
Chuck
Hmm, I'm getting an error: `NoMethodError: undefined method ‘module_eval’ for #<Test:0x10011e148 @content=[1, 2, 3]>` Does `Test` have to inherit from a special class?
JP
Oh, weird. Instances can extend Forwardable in Ruby 1.9, but not in Ruby 1.8. Tested this new version in both and it should work fine.
Chuck
Epic! Thanks so much
JP
+3  A: 
module Stringy
       def foo
           puts "I am a stringy"
       end
end

module Inty
       def bar
           puts "I am inty"
       end
end


class Test
      def initialize(mod_type)
          self.extend(mod_type)
      end
end


test = Test.new(Stringy)
cgr
And of course Test#initialize logic means it could be rewritten to accept a string and determine the class required according to whatever rules it needs - which is the plan! Cheers!
JP
A: 

Check DelegateClass and Forwardable, I think this is more the direction you need to go (universal facade containers). You don't need to have classes for them per s.e. since a class is just an object and not purely a syntactic construct.

Julik