Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build a JSON response made up of multiple models in Rails

First, the desired result

I have User and Item models. I'd like to build a JSON response that looks like this:

{
  "user":
    {"username":"Bob!","foo":"whatever","bar":"hello!"},

  "items": [
    {"id":1, "name":"one", "zim":"planet", "gir":"earth"},
    {"id":2, "name":"two", "zim":"planet", "gir":"mars"}
  ]
}

However, my User and Item model have more attributes than just those. I found a way to get this to work, but beware, it's not pretty... Please help...

Update

The next section contains the original question. The last section shows the new solution.


My hacks

home_controller.rb

class HomeController < ApplicationController

  def observe
    respond_to do |format|
      format.js { render :json => Observation.new(current_user, @items).to_json }
    end
  end

end

observation.rb

# NOTE: this is not a subclass of ActiveRecord::Base
# this class just serves as a container to aggregate all "observable" objects
class Observation
  attr_accessor :user, :items

  def initialize(user, items)
    self.user = user
    self.items = items
  end

  # The JSON needs to be decoded before it's sent to the `to_json` method in the home_controller otherwise the JSON will be escaped...
  # What a mess!
  def to_json
    {
      :user => ActiveSupport::JSON.decode(user.to_json(:only => :username, :methods => [:foo, :bar])),
      :items => ActiveSupport::JSON.decode(auctions.to_json(:only => [:id, :name], :methods => [:zim, :gir]))
    }
  end
end

Look Ma! No more hacks!

Override as_json instead

The ActiveRecord::Serialization#as_json docs are pretty sparse. Here's the brief:

as_json(options = nil) 
  [show source]

For more information on to_json vs as_json, see the accepted answer for Overriding to_json in Rails 2.3.5

The code sans hacks

user.rb

class User < ActiveRecord::Base

  def as_json(options)
    options = { :only => [:username], :methods => [:foo, :bar] }.merge(options)
    super(options)
  end

end

item.rb

class Item < ActiveRecord::Base

  def as_json(options)
    options = { :only => [:id, name], :methods => [:zim, :gir] }.merge(options)
    super(options)
  end

end

home_controller.rb

class HomeController < ApplicationController

  def observe
    @items = Items.find(...)
    respond_to do |format|
      format.js do
        render :json => {
          :user => current_user || {},
          :items => @items
        }
      end
    end
  end

end
like image 249
maček Avatar asked Apr 03 '10 19:04

maček


1 Answers

EDITED to use as_json instead of to_json. See How to override to_json in Rails? for a detailed explanation. I think this is the best answer.

You can render the JSON you want in the controller without the need for the helper model.

def observe
  respond_to do |format|
    format.js do
      render :json => {
        :user => current_user.as_json(:only => [:username], :methods => [:foo, :bar]),
        :items => @items.collect{ |i| i.as_json(:only => [:id, :name], :methods => [:zim, :gir]) }
      }
    end
  end
end

Make sure ActiveRecord::Base.include_root_in_json is set to false or else you'll get a 'user' attribute inside of 'user'. Unfortunately, it looks like Arrays do not pass options down to each element, so the collect is necessary.

like image 165
Jonathan Julian Avatar answered Nov 02 '22 07:11

Jonathan Julian