views:

88

answers:

3

I am trying to dynamically create a class using the eval method. It is working fine except for one small problem. As my code shows I am creating the Browser class inside the BrowserFactory class. When I do this the Browser class has an added namespace of BrowserFactory. Is there anyway to evaluate the Browser class from a string without the BrowserFactory namespace being attached?


class BrowserFactory
  def self.create_browser(browser)
    super_class = nil
    case browser
    when 'IE'
      require 'watir'
      super_class = 'Watir::IE'
    when 'celerity'
      require 'celerity'
      super_class = 'Celerity::Browser'
    end

    raise StandardError.new("Browser '#{browser}' is not currentlys supported") if super_class.nil?

    eval <<EOS
class Browser < #{super_class}
include Singleton
include BrowserModification
end
EOS

    return Browser.instance
  end

end
+2  A: 

Change

class Browser < #{super_class}

to

class ::Browser < #{super_class}
Simone Carletti
+3  A: 

Defining Browser (or ::Browser, to directly answer your question) will prevent you from calling your factory more than once.

I would recommend to use an anonymous class. No need for eval, btw, and you can define the class method to_s if you want to:

class BrowserFactory
  def self.create_browser(browser)
    super_class = case browser
    when 'IE'
      require 'watir'
      Watir::IE
    when 'celerity'
      require 'celerity'
      Celerity::Browser
    else
      raise StandardError.new("Browser '#{browser}' is not currentlys supported")
    end

    klass = Class.new(super_class) do
      include Singleton
      include BrowserModification
      def self.to_s
        "Modified#{superclass}"
      end
    end
    klass.instance
  end
end
Marc-André Lafortune
You can actually just pass the setup block to `Class.new` rather than call `class_eval` separately.
Chuck
Indeed, even nicer. Thanks :-)
Marc-André Lafortune
A: 
def BrowserFactory(browser)
  case browser
  when 'IE'
    require 'watir'
    Watir::IE
  when 'celerity'
    require 'celerity'
    Celerity::Browser
  else
    raise ArgumentError, "Browser '#{browser}' is not currently supported"
  end.new.extend(BrowserModification)
end

Here's a small testsuite:

module Watir; class IE; def to_s; 'IE' end end end
module Celerity; class Browser; def to_s; 'Celerity' end end end

module BrowserModification; def to_s; "Modified#{super}" end end

require 'test/unit'
class TestBrowserFactory < Test::Unit::TestCase
  def test_that_celerity_responds_as_modified_celerity
    assert_equal 'ModifiedCelerity', BrowserFactory('celerity').to_s
  end
  def test_that_internet_explorer_responds_as_modified_internet_explorer
    assert_equal 'ModifiedIE', BrowserFactory('IE').to_s
  end
  def test_that_an_invalid_browser_raises_an_exception
    assert_raise ArgumentError do BrowserFactory('xyz') end
  end
end
Jörg W Mittag