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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With