ruby-on-rails-3jsonserialization

How to include class name when calling to_json in rails3?


I'm trying to implement autocomplete that lets user to pick from list of 2 different kind of models.

This is how my controller looks:

def ac
  arr = []
  arr << Foo.all
  arr << Bar.all
  render json: arr.to_json
end

Which renders:

[[{"id":1, "name":"foo name"}], [{"id":1, "name":"bar name"}]]

How to include class name and get something like this:

[
 [{"id":1, "name":"foo name", "class_name":"Foo"}],
 [{"id":1, "name":"bar name", "class_name":"Bar"}]
]

?


Solution

  • If you don't mind doing a bit of extra work you can do smth like that with :methods option of as_json method (and to_json as well):

    class Foo
      def class_name
        self.class.name
      end
    end
    
    arr = Foo.all.map { |foo| foo.as_json(:methods => [:class_name]) }
    puts arr.to_json
    #=> [{ "id": 1, "name": "foo name", "class_name": "Foo" }]
    

    If you have ActiveRecord::Base.include_root_in_json set to true (that is default afaik) then you'll get hashes like

    { "foo": { "id": 1, "name": "foo name" } }
    

    If you want it to be exactly the class name you can pass :root option:

    foo = Foo.last
    puts foo.to_json(:root => foo.class.name)
    #=> { "Foo": { "id": 1, "name": "foo name" } }
    

    Note that both these solutions do not allow you simply to call to_json on an array of records. To overcome that and make class_name included by default you can override serializable_hash method in your model like that:

    def serializable_hash(*)
      super.merge('class_name' => self.class.name)
    end
    

    If you wrap it into a module you can include it in any model you want and get class_name included into the result of as_json or to_json without passing any extra options to these methods. You can modify the implementation a bit to respect :except option if you want to exclude class_name in some cases.