TL;DR overwrite as_json on all your CanCan Ability classes.

Since I’ve started using squash for bug tracking I started noticing exceptions which prevented squash client from transmitting exceptions to the squash backend.

The exceptions squash was rescueing made squash raise a new exception!

Digging through the stack traces it became obvious that Ryan Bates cancan is the root of all evil.

To reproduce fire up irb and paste the following lines:

# dependencies to reproduce
require 'cancan'         # v1.6.10
require 'json'           # v1.7.7
require 'active_support' # v3.2.12
require 'active_record'
require 'sqlite3'        # v1.3.7

# we need active record
ActiveRecord::Base.establish_connection({
  :adapter => "sqlite3",
  :database  => ":memory:"
})

# and two sample classes
class User < ActiveRecord::Base
end

ActiveRecord::Migration.create_table :users do |t|
end

class Project < ActiveRecord::Base
  belongs_to :user
end

ActiveRecord::Migration.create_table :projects do |t|
  t.belongs_to :user
end

# as well as an Ability
class Ability
  include CanCan::Ability

  def initialize user
    user

    can [:new, :create, :edit, :update, :destroy], Project do |project|
      project.user == user
    end
  end
end

ability = Ability.new(User.new)
ActiveSupport::JSON.encode(ability) # => boom

You’ll see an ActiveSupport::JSON::Encoding::CircularReferenceError: object references itself exception.

As far as I can tell this is due to the data structure CanCan is using to store the permissions.

Defining as_json as follows solves this problem:

def as_json *args
  return {
    class_name: 'Ability',
    user_id: null # your metadata here
  }
end

I’d vote to make this a default in CanCan, but for the time being this solves my problem while still leaving enough debugging informations for me to reproduce error states.

That being said, I really like cancan - it’s a great gem. But those circular reference errors are something to watch out for.


comments powered by Disqus