Include associated model when rendering JSON in Rails - ruby-on-rails

Right now I have this line:
render json: #programs, :except => [:created_at, :updated_at]
However, since a Program belongs_to a Company I would like to show the Company name instead of the Company Id.
How can I include the company name when rendering Programs?

Something like this should work:
render :json => #programs, :include => {:insurer => {:only => :name}}, :except => [:created_at, :updated_at]

i was getting the same "can't clone Symbol file" error while rendering json with includes from a controller method. avoided it like so:
render :json => #list.to_json( :include => [:tasks] )

You can also do this at the model level.
program.rb
def as_json(options={})
super(:except => [:created_at, :updated_at]
:include => {
:company => {:only => [:name]}
}
)
end
end
Now in your controller:
render json: #programs

Consider using jbuilder to include nested models in a maintainable way:
# /views/shops/index.json.jbuilder
json.shops #shops do |shop|
# shop attributes to json
json.id shop.id
json.address shop.address
# Nested products
json.products shop.products do |product|
json.name product.name
json.price product.price
end
end

Try this. Ref
#`includes` caches all the companies for each program (eager loading)
programs = Program.includes(:company)
#`.as_json` creates a hash containing all programs
#`include` adds a key `company` to each program
#and sets the value as an array of the program's companies
#Note: you can exclude certain fields with `only` or `except`
render json: programs.as_json(include: :company, only: [:name])
Also, no need to make #programs an instance variable, as I'm assuming we are not passing it to a view.

#includes is used to avoid n+1 query.
# http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
Here is an example for the above example.Lets say you have posts and each post has many comments to it.
#posts = Post.where('id IN [1,2,3,4]').includes(:comments)
respond_to do |format|
format.json {render json: #posts.to_json(:include => [:comments]) }
end
#output data
[
{id:1,name:"post1",comments:{user_id:1,message:"nice"}}
{id:2,name:"post2",comments:{user_id:2,message:"okok"}}
{id:3,name:"post1",comments:{user_id:12,message:"great"}}
{id:4,name:"post1",comments:{user_id:45,message:"good enough"}}
]

Related

Pass one variable to next in JSON object in Rails 5

I need to be able to access user data in comments.
def index
#user = User.all
#libraries = Library.all.order('created_at ASC')
#comments = Comment.all
#user_likes = UserLike.all
render :json => #libraries, :include => [:user, :comments, :user_likes]
end
I tried this:
render :json => #libraries, :include => [:user, {:comments => :user}, :user_likes]
And it did not work. Any ideas?
Assuming you have the associations set up, you need to add include for the nested associations:
render :json => #libraries,
:include => {:user, {:comments => { include: :user}}, :user_likes}
When rendering JSON like this, Rails calls as_json first to convert the payload to hash. That's when the option include is used. You can check the documentation to see how it works.

How to override how my classes property is rendering the JSON for my order model

I am currently doing this in my controller action:
render json: order.to_json(:include => [items: {include: [products: {include: [:inventory]}]}])
Now I am moving this over to a richer object like this:
class OrderResponse
attr_accessor :success, :errors, :order, :users
def initialize(success, errors)
#success = success
#errors = errors
end
end
The problem now is that it doesn't render all the inner data of the order model.
The json now has only the order attributes since I am not calling the :includes now.
response = OrderResponse.new(true, [])
response.order = order
response.user = user
render json: response
How can I override how the order is being converted to json now?
Just like you can create .html templates using the ERB templating language (or others), you can use the jbuilder DSL to render complex json responses.
OrdersController:
#response = OrderResponse.new(true, [])
render :response, formats: [:json]
views/orders/response.json.jbuilder
json.call #response, :id
json.user do
json.extract! #response.user, :id, :created_at # ...
end
json.order do
json.extract! #response.order, :id, :created_at # ...
end

Serialize JSON nested model in Ruby on Rails

I am new to rails and I have two models, one has a foreign key on the other model.
I created a controller and defined and index method which is working fine:
def index
#collections = Collection.all
render json: #collections
end
Which is rendering something like this:
[{"id":1,"title":"Collection
A","book_id":1,"created_at":"2016-04-10T18:41:32.709Z","updated_at":"2016-04-10T18:41:32.709Z"}]
I would like to transform that book_id field into a list of book objects, something like this:
[{"id":1,"title":"Collection A","books": [{"book_id": 1, "title:
"book_title"},],"created_at":"2016-04-10T18:41:32.709Z","updated_at":"2016-04-10T18:41:32.709Z"}]
Then I tried with:
def index
#collections = Collection.all
render :json => collections.as_json(
:include => { :book_title }
)
end
But is giving me syntax error and I cannot see how to do it properly in this doc http://guides.rubyonrails.org/layouts_and_rendering.html
I am using Rails 4.1.0
Try:
def index
#collections = Collection.all
render :json => #collections.as_json(
:include => :book
)
end
or if you would like just the :id and :title:
def index
#collections = Collection.all
render :json => #collections.as_json(
:include => {:book => {:only => [:id, :title]}}
)
end

Thinking Sphinx: search across multiple models: best practices?

I want to add a jquery autocomplete with categories.
The request will search across multiples models (Forum topics, news, users...) with Thinking Sphinx
So in controller, I think it will look like that
def autocomplete
#news = Actu.search(params[:term]).map {|g| {:label => g.title, :category => "Actualités", :id => g.id}}
#topics = Topic.search(params[:term]).map {|g| {:label => g.title, :category => "Topics", :id => g.id}}
#anotherModel = ...
respond_to do |format|
format.js { render :json => #news+#topics+#anotherModel }
end
end
That working, but what do you think about these practice ?
You can try this awesome syntax
ThinkingSphinx.search 'pancakes', :classes => [Article, Comment]
Read more at http://freelancing-god.github.com/ts/en/searching.html
You can search across all indexed models in your application:
ThinkingSphinx.search(params[:term])
Then you can define for each model method, say autocomplete_json, that returns hash.
So, your action
def autocomplete
render :json => ThinkingSphinx.search(params[:term]).map(&:autocomplete_json)
end

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
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.
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.
There are a lot of new Gems for building JSON now, for this case the most suitable I have found is Jsonify:
https://github.com/bsiggelkow/jsonify
https://github.com/bsiggelkow/jsonify-rails
This allows you to build up the mix of attributes and arrays from your models.
Working answer #2 To avoid the issue of your json being "escaped", build up the data structure by hand, then call to_json on it once. It can get a little wordy, but you can do it all in the controller, or abstract it out to the individual models as to_hash or something.
def observe
respond_to do |format|
format.js do
render :json => {
:user => {:username => current_user.username, :foo => current_user.foo, :bar => current_user.bar},
:items => #items.collect{ |i| {:id => i.id, :name => i.name, :zim => i.zim, :gir => i.gir} }
}
end
end
end

Resources