On Nov 6, 2008, at 8:01 AM, David Chelimsky wrote:

I've really moved away from shared example groups and started writing
more targeted macros. So I might do something like this:

def for_roles *roles
 roles.each do |role|
   before(:each) { login_as role }
   yield
 end
end

describe OrdersController do
 describe "GET index" do
   for_roles :admin, :sysadmin do |role|
     it "..." do ... end
   end
   for_roles :sysadmin do |role|
     it "..." do ... end
   end
 end

I was attempting to follow this example and discovered that before(scope, &block) is an alias for append_before which, as the method name indicates, appends the before block to the existing collection of before blocks.

So, in your example code above it seems that login_as would get called for each role passed to for_roles before each example is executed. Since the before blocks are stored internally as an array, the last element of the array would win.

Another example:

require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')

def for_roles *roles
  roles.each do |role|
    before(:each) do
      puts role.to_s
    end
    yield
  end
end

describe "DRY roles test" do

  describe "GET index" do
    for_roles :admin, :sysadmin do |role|
      it "..." do
        puts 'in example'
      end
    end
  end

end

Running this sample spec from the command line generates this output:

admin
sysadmin
in example
.admin
sysadmin
in example
.

I noticed that there is a method called 'remove_after' in before_and_after_hooks.rb but no corresponding remove_before. Looking at the code:

      def remove_after(scope, &block)
        after_each_parts.delete(block)
      end

I noticed that this method doesn't use the scope parameter at all and just attempts to delete the block from after_each_parts. So, I modified the method to honor the scope parameter and wrote a corresponding remove_before method:

      def remove_after(scope, &block)
        parts = after_parts_from_scope(scope)
        parts.delete(block)
      end

      def remove_before(scope, &block)
        parts = before_parts_from_scope(scope)
        parts.delete(block)
      end

However, this doesn't work as expected because if you do something like:

before(:each) { login_as role }
remove_before(:each) { login_as role }

two different Proc objects are created by each method call so parts.delete(block) will always fail. The only way I could see around this was if there were versions of append_before and remove_before which took a Proc object as a parameter instead of converting the block so you could maintain a reference to it in the calling code, i.e.

      def append_before_proc(*args, block)
        scope, options = scope_and_options(*args)
        parts = before_parts_from_scope(scope)
        parts << block
      end

      def remove_before_proc(scope, block)
        parts = before_parts_from_scope(scope)
        parts.delete(block)
      end

my_block = Proc.new { login_as role }
append_before_proc(:each, my_block)
remove_before_proc(:each, my_block)

Which seems like a lot of monkey patching to get this working. So now (finally) my questions:

1) Is there a better way to do this?
2) The current version of remove_after seems broken. Should I report this as a bug?

Thanks,
-Jesse
_______________________________________________
rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to