25

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 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

5 Answers 5

34

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.

Sign up to request clarification or add additional context in comments.

Comments

3

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.

1 Comment

This works, but I prefer to override serializable_hash, which also affects to_xml.
0

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.

1 Comment

Thanks. On further research, I ran into this gem which solves the original problem I set out to solve - github.com/fabrik42/acts_as_api
0

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

Comments

0

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

1 Comment

(this is a change for the answer below, for an unkown reason, it is shown here above it, sorry)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.