I have Rails 3 application with Cancan 1.6.9 for authorization. I want to convert Ability class to JSON. Here is the my Ability class.
# encoding: utf-8
module Ability
def self.for(guest)
guest ||= User.new # guest user (not logged in)
if guest.admin?
AdminAbility.new(guest)
elsif guest.assurer?
AssurerAbility.new(guest)
end
end
##
# Assurer
class AssurerAbility
include CanCan::Ability
def initialize(user)
cannot :manage, User
can :read, ExchangeRate
end
end
##
# Admin
class AdminAbility
include CanCan::Ability
def initialize(user)
can :manage, User
can :manage, ExchangeRate
end
end
end
When I run Ability.for(User.last).to_json
I get following errors:
ActiveSupport::JSON::Encoding::CircularReferenceError: object references itself
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:75:in `check_for_circular_references'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:46:in `encode'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `block in encode_json'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `each'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `map'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `encode_json'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:48:in `block in encode'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:77:in `check_for_circular_references'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:46:in `encode'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `block in encode_json'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `each'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `map'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `encode_json'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:48:in `block in encode'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:77:in `check_for_circular_references'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:46:in `encode'
... 19 levels...
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:46:in `encode'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `block in encode_json'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `each'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `map'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `encode_json'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:48:in `block in encode'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:77:in `check_for_circular_references'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:46:in `encode'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:31:in `encode'
from /home/user/.rvm/gems/[email protected]/gems/activesupport-3.2.13/lib/active_support/core_ext/object/to_json.rb:16:in `to_json'
enter code here
And when I run Ability.for(User.last).as_json
I get result. But it's not valid JSON result.
{"rules"=>[#<CanCan::Rule:0xaf5e81c @match_all=false, @base_behavior=false, @actions=[:manage], @subjects=[User(id: integer, password_digest: string, created_at: datetime, updated_at: datetime, uic: string, role: string, name: string, register_no: string)], @conditions={}, @block=nil>, #<CanCan::Rule:0xaf5e72c @match_all=false, @base_behavior=true, @actions=[:read], @subjects=[ExchangeRate(id: integer, data: float, date: date, created_at: datetime, updated_at: datetime)], @conditions={}, @block=nil>]}
Any idea?
TL;DR: Implement self.as_json
method in your models and explicitly specify which fields to convert to JSON. After this you will be able to run Ability.for(User.last).to_json
without errors.
Complete explanation: let's look at the structure of Ability
as seen from source:
Ability
instance has a field named @rules
.@subject
which is a flattened ActiveRecord class (User
, ExchangeRate
etc). Yes, class, not class instance.@subject
gets converted to JSON and CircularReferenceError
appears. Why?The problem is, converting User
or ExchangeRate
class to JSON is not a good idea because their structure may be very complicated. For instance, if you try running User.as_json
, the result might be like this. Probably one of User
's fields references back to User
and you get CircularReferenceError
. To avoid this, specify which fields should be included into JSON representation of User
(the same is applied for ExchangeRate
):
class User < ActiveRecord::Base
#...
def self.as_json(options = {})
{ "class" => "User" }
end
end