On 5/23/10, James Wenton <kkaitan / gmail.com> wrote:
> I'm a little stumped by this problem. Here's some simple code that,
> for each argument specified, will add specific get/set methods named
> after that argument. If you write `attr_option :foo, :bar`, then you
> will see `#foo/foo=` and `#bar/bar=` instance methods on `Config`:
>
>     module Configurator
>       class Config
>         def initialize()
>           @options = {}
>         end
>
>         def self.attr_option(*args)
>           args.each do |a|
>             if not self.method_defined?(a)
>               define_method "#{a}" do
>                 @options[:"#{a}"] ||= {}
>               end
>
>               define_method "#{a}=" do |v|
>                 @options[:"#{a}"] = v
>               end
>             else
>               throw Exception.new("already have attr_option for #{a}")
>             end
>           end
>         end
>       end
>     end
>
> So far, so good. I want to write some RSpec tests to verify this code
> is actually doing what it's supposed to. But there's a problem! If I
> invoke `attr_option :foo` in one of the test methods, that method is
> now forever defined in Config. So a subsequent test will fail when it
> shouldn't, because `foo` is already defined:
>
>       it "should support a specified option" do
>         c = Configurator::Config
>         c.attr_option :foo
>         # ...
>       end
>
>       it "should support multiple options" do
>         c = Configurator::Config
>         c.attr_option :foo, :bar, :baz   # Error! :foo already defined
>                                          # by a previous test.
>         # ...
>       end
>
> Is there a way I can give each test an anonymous "clone" of the
> `Config` class which is independent of the others?

As I see it, you have several options:

1) remove the exception you are raising at the end of attr_option.
2) intercept and ignore that exception when you call attr_option in your tests.
3) remove the methods you added at the end of each test. Something
like this should work:
     c.send :undef_method, :foo
4) (what you were asking about) make a copy of Configurator::Config
before changing it. This should work:
     c=Configurator::Config.clone


PS: c is an especially unlucky choice for a local variable name, I
have found. If you ever have to run your program under one of the
console-mode debuggers (I do this all the time) it will get confused
with the continue command, which is abbreviated c, often with highly
frustrating results.