How to build a JSON response made up of multiple models in Rails
Solution 1
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.
Solution 2
Incase anyone is looking for an alternative solution for this, this is how I solved this in Rails 4.2:
def observe
@item = some_item
@user = some_user
respond_to do |format|
format.js do
serialized_item = ItemSerializer.new(@item).attributes
serialized_user = UserSerializer.new(@user).attributes
render :json => {
:item => serialized_item,
:user => serialized_user
}
end
end
end
This returns the serialized version of both objects as JSON, accessible via response.user
and response.item
.
maček
Updated on June 05, 2022Comments
-
maček almost 2 years
First, the desired result
I have
User
andItem
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
andItem
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
insteadThe ActiveRecord::Serialization#as_json docs are pretty sparse. Here's the brief:
as_json(options = nil) [show source]
For more information on
to_json
vsas_json
, see the accepted answer for Overriding to_json in Rails 2.3.5The 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
-
owl about 14 yearsThe hash syntax you're using is only from 1.9 and may confuse anyone who's not familiar with it. May I suggest changing it to be the standard
"user" => current
that we all "know and love"? -
maček about 14 years@Jonathan Julian,
ActiveRecord::Base.include_root_in_json
is set tofalse
and this is doing exactly what I expected, but not exactly what I hoped for. The internalto_json
calls are getting escaped byrender :json
. For example, instead of{"user": {"username": "Bob!"}}
I am getting{"user": "{\"username\": \"Bob!\"}"}
:( -
Jonathan Julian about 14 years@ryan fixed hash syntax to be ruby 1.8 style
-
maček about 14 years@Ryan Bigg, it's actually a typo. (And a syntax error, ever for Ruby 1.9). He means
{:user => current_user...}
-
Jonathan Julian about 14 years@smotchkkisss You can always
render :json => {}
and just build up that hash by hand without calling to_json on the models. Or usedecode
as you've already found. Either way, there's no need for a separate model. -
maček about 14 years@Jonathan Julian, thanks for giving this a shot. You might want to make a "does not work" note somewhere in your answer so people don't chase down the same dead end I did. +1 for effort
-
maček about 14 yearsSee why I had my "Override
to_json
in Rails 2.3.5" (stackoverflow.com/questions/2572284/…) question before? ;) -
Jonathan Julian about 14 yearsTo solve both your problems, create a
to_hash
in each of your models and build them yourself. Trade off a bit of code for a couple headaches. -
maček about 14 yearsplease update
@items.each
to read@items.collect
.Array#each
returns the original@items
array. This is only mildly cleaner than what I had before, but it still seems like I should be able to tap into theas_json
orto_json
methods somehow; I mean, that's what they're for. It's very possible that many more objects will be appearing in the "Observation" hash, so doing all this extra work for each one seems like it could be a headache of its own. If a more suitable answer doesn't appear in a couple days, I'll mark this as accepted. Thanks again :) -
maček about 14 years@Ryan Bigg, I looked into this new syntax a bit.
{a: "foo"}
is valid,{'a': "foo"}
is not. Like you, I still prefer the{:foo => "bar"}
notation :) -
maček about 14 years@Jonathan Julian, thanks again. I updated the original question to show the application of the new
as_json
override as well. This make me very happy :)