I just cooked up a macro where I needed to access an instance variable from the setup blocks. However the scope of should statements are at the class level, so we don’t have access to instance variables from there (only class variables). I found the solution inside the code for should_redirect_to, which evals the object in the binding of the should block.


class ProjectsControllerTest < ActionController::TestCase
  def setup
    super
    @project = create_project
  end

  %w[ client developer ].each do |role|
    context "A #{role} user" do
      setup do
        login_as( user = create_user )
        @project.create_role_for!( user, role )
      end

      should_not_allow :destroy, "@project" 
      should_not_allow :edit, "@project"    
    end
  end
end
This next file goes in test/shoulda_macros/*.rb, which will be auto-loaded:

Test::Unit::TestCase.class_eval do

  def self.should_not_allow action, object, msg=nil
    msg ||= "a #{object.class.to_s.downcase}" 
    should "not be able to #{action} #{msg}" do
      object = eval(object, self.send(:binding), __FILE__, __LINE__)
      get action, :id => object.id
      assert_response 404
    end
  end

  def self.should_allow action, object, msg=nil
    msg ||= "a #{object.class.to_s.downcase}" 
    should "be able to #{action} #{msg}" do
      object = eval(object, self.send(:binding), __FILE__, __LINE__)
      get action, :id => object.id
      assert_response :success
    end
  end

end

1 Response to “more on custom shoulda macros: scoping of instance variables”

  1. fractious Says:

    Cheers David, just what I was looking for. There seems to be one issue with it though, the default msg always reports object as a string as it hasn't been evaled yet.

    Oh and for anyone else trying to use these macros, make sure you set:

    config.actioncontroller.considerallrequestslocal = false

    in config/environments/test.rb otherwise you'll probably just get an uncaught ActiveRecord::RecordNotFound when calling shouldnotallow.

Leave a Reply