defaults for to_json in Rails with :include
Let us say I have a model Post which belongs to a User. To convert to json, I do something like this
@reply.to_json(:include => {:user => {:only => [:email, :id]},
:only => [:title, :id])
However, I want to set开发者_JAVA技巧 some defaults for this so I don't have to specify :only everytime. I am trying to override as_json to accomplish this. When I add as_json in User model, it is called when I do @user.to_json but when user is included in @reply.to_json, my overriden as_json for User is ignored.
How do I make this work?
Thanks
You can do this by overriding serializable_hash
in your model, like so:
class Reply < ActiveRecord::Base
def serializable_hash(options={})
options = {
:include => {:user => {:only => [:email, :id]},
:only => [:title, :id]
}.update(options)
super(options)
end
end
This will affect all serializable methods, including serializable_hash
, to_json
, and to_xml
.
In the Reply model:
def as_json(options = {})
super options.merge(:methods => [:user], :only => [:id, :title])
end
And in the User model:
def as_json(options = {})
super options.merge(:only => [:id, :email])
end
When the association is included as a method and not an include, it will run the .to_json on it.
You could add another method to ActiveRecord::Base, like self.to_minimal_json(*only_fields) and build your .to_json call there.
unspectacular, but may work.
So I found out that the problem is that for includes, instead of calling as_json to generate the json, Rails directly calls serializable_hash. Monkey patching fixes the problem. Put this in an initializer
module ActiveRecord #:nodoc:
module Serialization
def serializable_hash(options = nil)
options = options.try(:clone) || {}
options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
options[:except] |= Array.wrap(self.class.inheritance_column)
hash = super(options)
#serializable_add_includes(options) do |association, records, opts|
#hash[association] = records.is_a?(Enumerable) ?
#records.map { |r| r.serializable_hash(opts) } :
#records.serializable_hash(opts)
#end
serializable_add_includes(options) do |association, records, opts|
hash[association] = records.is_a?(Enumerable) ?
records.map { |r| r.as_json(opts.merge(:no_root => true)) } :
records.as_json(opts.merge(:no_root => true))
end
hash
end
end
end
module ActiveModel
# == Active Model JSON Serializer
module Serializers
module JSON
def as_json(options = nil)
hash = serializable_hash(options)
if include_root_in_json and !options[:no_root]
custom_root = options && options[:root]
hash = { custom_root || self.class.model_name.element => hash }
end
hash
end
end
end
end
Notice how the serializable_hash call is replaced with as_json (with an extra parameter to supress addition of root in includes). Now if you has as_json in your model, it will be called even in case of includes
As options might under circumstances be nil, we'd better be aware of it:
module ActiveModel
# == Active Model JSON Serializer
module Serializers
module JSON
def as_json(options)
options ||= {}
hash = serializable_hash(options)
if include_root_in_json and !options[:no_root]
custom_root = options && options[:root]
hash = { custom_root || self.class.model_name.element => hash }
end
hash
end
end
end
end
精彩评论