RSpec, Mocha, and Shoulda

The latest release of shoulda-matchers, 1.4.2, now requires Mocha.  I’ve been using the RSpec stubbing library, but adding mocha to the project breaks one little part of RSpec stubbing.  While Mocha uses stubs to stub a method and RSpec uses stub, both use unstub if you need to unstub a single method.  This collision causes problems if you’re using RSpec to create the stub, because the call to unstub uses Mocha which then promptly fails to remove the RSpec stub!

The solution is to use the RSpec alias unstub! wherever you need to remove an RSpec stub – since Mocha doesn’t implement unstub!, you can continue to happily use the RSpec stubbing library and still successfully unstub RSpec stubs.

Make declarative_authorization play nicely with fakefs during testing

It appears that the demand-loading of config/authorization_rules.rb has problems when fakefs is turned on. If the first test in a sequence to trigger declarative_authorization checks has fakefs turned on, none of the rules will get loaded. The odds of this are slim, but if you have rspec doing randomization the odds increase if you are rerunning a single spec file.

The workaround is simple. Just force declarative_authorization to load the rules before any tests run. Just add the following to your spec/spec_helper.rb file:

# This ensures authorization rules are loaded prior to tests that use FakeFS
Authorization::Engine.instance

Monkey patching I18n.t in Rails

During the Ruby Hangout on Nov 7th, I suggested that it would be cool if instead of writing t 'path.to.my.string', one could instead write t.path.to.my.string. Josh Szmajda made the mistake of agreeing, and so I decided this would be a good challenge for me to learn some more Ruby tricks.

First of all, I poked into I18n and made a good discovery – the translate method is not particularly useful without passing parameters. This meant that I could use the case where there’s an empty list of parameters to return a Funky Object(TM) that would do something cool with method_missing. Also, I decided that since :t is simply an alias for :translate, I could choose to write my own t that would defer to translate when passed args and do my own thing when it wasn’t. Basically, I could write something like:

require 'i18n'

module I18n
  class << self
    def t(*args)
      if (args.empty?)
        # Do something cool here!
      else
        translate(*args)
      end
    end
  end
end

I did some quick testing and discovered that did indeed work. Now on to the tricky part. Here’s the first version:

require 'i18n'

module I18n
  class << self
    def t(*args)
      if (args.empty?)
        translator = Object.new
        translator.instance_variable_set(:@path, '')
        def translator.method_missing(method_id, *args)
          raise StandardError, "No args please!" unless args.empty?
          @path = "#{@path}.#{method_id.to_s}"
          answer = I18n.translate(@path)
          answer.respond_to?(:keys) ? self : answer
        end
        translator
      else
        translate(*args)
      end
    end
  end
end

This creates a new Object object, then sets the instance variable @path on it to an empty string, and then sets up method_missing on that object to handle arbitrary methods. It looks them up using I18n.translate in whatever path is currently active. If the returned thing behaves like a Hash, then we presume we’re going to need to do this again, and so we just return self so the next method call can follow along. On the other hand, if we gotten to something that doesn’t look like a Hash, then we just return that and we’re done.

The only problem with this approach is that there are a whole bunch of methods defined in Object that can’t be used as keys. For instance, if you have the key inspect in your locale file, you’re out of luck getting to it with this notation.

So I decided to try to swap in BasicObject for Object. This took me a little longer to figure out since I still needed a way to call instance_variable_set. I finally came up with the following. It may not be the best way to handle this, but it’s what I stumbled upon.

module I18n
  class << self
    def t(*args)
      if (args.empty?)
        translator = BasicObject.new
        def translator.__instance_variable_set(sym, obj)
          ::Object.instance_variable_set(sym, obj)
        end
        translator.__instance_variable_set(:@path, '')
        def translator.method_missing(method_id, *args)
          raise StandardError, "No args please!" unless args.empty?
          @path = "#{@path}.#{method_id.to_s}"
          answer = I18n.translate(@path)
          answer.respond_to?(:keys) ? self : answer
        end
        translator
      else
        translate(*args)
      end
    end
  end
end

I add another singleton that can call ::Object.instance_variable_set for me. I have no idea what this syntax is actually doing – I stumbled upon it by looking at the documentation on BasicObject in the Pickaxe Book. But it works! When I use I18n.t.hash_name in irb I get translation missing: en.hash_name.inspect – note that .inspect on the end that results because it tries to look up the inspect message that irb sends the result!

I’m not at all sure whether this whole exercise was a good idea from a production code standpoint, but it was fun!

Using factory_girl with declarative_authorization

I recently added declarative_authorization to a Rails app that uses FactoryGirl with RSpec. As Mark Needham pointed out around two years ago at http://www.markhneedham.com/blog/2010/09/12/ruby-factorygirl-declarative_authorization-random-thoughts/, this can cause problems because FactoryGirl can’t save records without the appropriate authorization. Mark’s suggestion was to use monkey patching to wrap around the create method, but I had some issues attempting to implement this. I suspect the internals of FactoryGirl have changed since Mark posted.

After extensive poking around and trial and error, I stumbled across http://robots.thoughtbot.com/post/23039827914/get-your-callbacks-on-with-factory-girl-3-3, which has a whole discussion about callback support that was added in FactoryGirl 3.3.0 (which was less than six months old at the time of this posting). It first occurred to me to use the before(:create) and after(:create) to set Authorization.current_user, but after I read to the end of the post I realized that to_create was exactly what I needed.

All I had to do was to create the file spec/factories.rb and added the following code to it:

require 'declarative_authorization/maintenance'
include Authorization::TestHelper

FactoryGirl.define do
  to_create do |instance|
    without_access_control { instance.save! }
  end
end

Voila! No monkey patching, and I only have to create one file and all my calls to FactoryGirl.create start working again.