Having trouble with generating some json. I am trying to render an
single active record result to json like this:
#data = User.find(1)
respond_with(#data, :include => :status)
The json result is:
{
-user: {
address: null
email: "test#email.com"
first_name: "Test"
last_name: "Man"
status_id: 1
username: "testguy"
status: { }
}
}
So whats the problem? The problem is that the :include=>:status seems
to not bring over the relation. In my User model I have a
belongs_to :status. How do i get this to work on a single result set?
When I do this:
#data = User.where("id = 1")
respond_with(#data, :include => :status)
The relation shows in the json result set fine this way. But its
within an array of objects, which i do not want.
Any ideas?
I assume that the status you want to include is not one of the http status codes (200, 201, etc.) nor an object associated with the User object in any way and is your own variable.
Using Rails 3.0.10 and Ruby 1.9.2 you may have find one of these two solutions suitable for you. Instead of respond_with use render as follows.
Solution 1
render :json => {:data => #data, :status => "whatever"}
The json result is:
{
data:
{
user:
{
address: null
email: "test#email.com"
first_name: "Test"
last_name: "Man"
status_id: 1
username: "testguy"
}
}
status: "whatever"
}
Solution 2
render :json => {:user => {:address => #data.address, :email => #data.email, ..., :status => "whatever"}}
The json result is:
{
user:
{
address: null
email: "test#email.com"
...
status: "whatever"
}
}
I hope this is what you were looking for.
It sounds like you're having some of the same suffering I experienced with using the built in rails serialization. I ended up using RABL templates which made my life a lot easier. If you need a guide on getting up and running with RABL quickly, I've put one together:
http://blog.dcxn.com/2011/06/22/rails-json-templates-through-rabl/
where returns an array, so your results make sense. You'd need where().first, or since it's just an ID lookup, simply use find.
This was a long time ago, but still, this is the most concise answer:
#data = User.find(1)
respond_with(#data, :include => :status)
You might've solved it by now mate, but if you're still running into probs you can do it this way if you want?
#data = User.find(1, :include => :status)
respond_to do |format|
format.json do
render #data.to_json(:include => :status)
end
end
If that doesn't work there may be something wrong with your associations. There's a really good section in the api on 'Eager loading of associations' here:
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods
As nixterrimus pointed out RABL is really the way to go. It's simple, clean and elegant. I'm relatively new to rails and have not used these techniques (to_json, etc) but having done some research and actually using RABL, I cannot help but wonder why it's not a native feature of Rails.
Related
I have the followin Ruby + Rails code
render :json => enterprise.to_json(:include => { :v3_passengers => { :include => [:cost_center, :restrictions]}})
And I need to apply a WHERE filter using one of the fields of the v3_passengers model before rendering it as json (for example "where v3_passenger.id = 2345")
I have tried this
render :json => enterprise.includes(:v3_passengers).where(enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY).includes(:cost_center, :restrictions).to_json
But is not working, I have looked arround whitout any look in how to achieve this.
UPDATE
This are how the models are related
class Enterprise < ActiveRecord::Base
has_many :v3_passengers
class V3Passenger < GlobalDB
has_many :restrictions
belongs_to :cost_center
1. First you need to filter by joins or includes:
foo = enterprise.joins(:v3_passengers).where(v3_passengers: {enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY})
or (prefered includes, since you are going to need v3_passengers )
foo = enterprise.includes(:v3_passengers).where(v3_passengers: {enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY})
2. Then include the other nodes you need in the to_json:
foo.to_json(include: [v3_passengers: { include: [:cost_center, :restrictions] } ])
Final Result:
render :json => enterprise.joins(:v3_passengers).where(v3_passengers: {enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY}).to_json(include: [v3_passengers: { include: [:cost_center, :restrictions] } ])
The problem is that:
model.includes(:other_model).to_json
Isn't the same as:
model.to_json(include: :other_model)
So your first attempt is giving you all the fields of Enterprise, V3Passenger, Restriction and CostCenter in the output. Your second attempt is just giving you fields of Enterprise.
One potential fix is:
enterprise.joins(:v3_passengers).where("v3_passengers.id=?",2345).to_json(include: :v3_passengers)
(Including the other tables of course.)
This will give you JSON for all the Enterprises with v3_passengers.id=2345, including JSON for all their V3Passengers (even the V3Passengers who don't have id 2345).
If you only want to include V3Passengers who match the where clause then you need to add a scoped association to the model:
has_many :v3_passengers_where_id_2345, -> { where id: 2345 }
And then use that association when doing the JSON conversion:
enterprise.joins(:v3_passengers).where("v3_passengers.id=?",2345).to_json(include: :v3_passengers_where_id_2345)
This will give you JSON for enterprises who have v3_passengers.id=2345, including only their V3Passengers who have id 2345.
The second shot is close to working variant.
render :json => enterprise.v3_passengers.where(enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY).includes(:cost_center, :restrictions).to_json
Try to use some relation.
For more clear answer add your key models listings, Enterprice and passengers models.
if you need the enterprise attributes in the resulting json:
render :json => enterprise.joins(:v3_passengers).where("v3_passengers.enterprise_country = ?", Thread.current['CurrentBehaviour'].COUNTRY).to_json(include: [v3_passengers: { include: [:cost_center, :restrictions] } ])
if you just need the passengers:
render :json => enterprise.v3_passengers.where(enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY).includes(:cost_center, :restrictions).to_json( include: [:cost_center, :restrictions])
def jsontest
#users = User.all.limit(10)
render json: #users
end
yields
{
...
"id": 7,
"name": "Sage Smith",
"email": "example-6#railstutorial.org",
"created_at": "2013-10-17T02:29:15.638Z",
"updated_at": "2013-10-17T02:29:15.638Z",
"password_digest": "$2a$10$taHk3udtWN61Il5I18akj.E90AB1TmdL1BkQBKPk/4eZ7YyizGOli",
"remember_token": "118f807d0773873fb5e4cd3b5d98048aef4f6f59",
"admin": false
...
}
But I would like to omit certain certain fields from this API, so I use pluck
def jsontest
#users = User.all.limit(10).pluck(:id, :name, :email, :created_at) ###
render json: #users
end
but pluck returns an array of only values, when I would like to have each object's attributes accessible by hash key.
[
...
7,
"Sage Smith",
"example-6#railstutorial.org",
"2013-10-17T02:29:15.638Z"
...
]
So how can I effectively pluck the values and their keys?
I realize I could sweep through #users and grab the keys before plucking and recreate the hash, but I'm expecting there to be some convenience method that does exactly what I want.
vee's answer is good, but I have one caveat. select instantiates a User for every row in the result, but pluck does not. That doesn't matter if you are only returning a few objects, but if you are returning large batches (50, 100, etc) you'll pay a significant performance penalty.
I ran into this problem, and I switched back to pluck:
#in user.rb
def self.pluck_to_hash(*keys)
pluck(*keys).map{|pa| Hash[keys.zip(pa)]}
end
#in elsewhere.rb
User.limit(:10).pluck_to_hash(*%i[id name email created_at])
It's ugly, but it gets the hash you want, and fast.
I've updated it to reflect Mike Campbell's comment on Oct 11.
Use select instead of pluck:
def jsontest
#users = User.select('id, name, email, created_at').limit(10)
render json: #users
end
Created a simple pluck_to_hash gem to achieve this.
https://github.com/girishso/pluck_to_hash
Usage example..
Post.limit(2).pluck_to_hash([:id, :title])
#
# [{:id=>213, :title=>"foo"}, {:id=>214, :title=>"bar"}]
#
Post.limit(2).pluck_to_hash(:id)
#
# [{:id=>213}, {:id=>214}]
#
# Or use the shorter alias pluck_h
Post.limit(2).pluck_h(:id)
#
# [{:id=>213}, {:id=>214}]
#
Fastest way to return hash of users with selected columns is use ActiveRecord::Base.connection.select_all method.
sql = User.select('id, name, email, created_at').limit(10).to_sql
#users = ActiveRecord::Base.connection.select_all(sql)
render json: #users
#andrewCone - It should be like this:
User.limit(:10).pluck_to_hash([:id, :name, :email, :created_at])
You can pull the data along with the column name as:
#users = User.all.limit(10)
.pluck(:id, :name, :email, :created_at)
.map {|id, name, email, created_at| { id: id, name: name,
email: email,
created_at: created_at } }
This will pull the data and map it according to how you want it. One advantage of using pluck over select is that you can use joins along with it.
#girishso's gem is great, but my project is in Rails 3. It doesn't work.
This article is helpful to me, or install pluck_all gem to achieve this.
Usage:
User.limit(10).pluck_all(:id, :name, :email, :created_at)
Let's say I have an app that handles a TODO list. The list has finished and unfinished items. Now I want to add two virtual attributes to the list object; the count of finished and unfinished items in the list. I also need these to be displayed in the json output.
I have two methods in my model which fetches the unfinished/finished items:
def unfinished_items
self.items.where("status = ?", false)
end
def finished_items
self.items.where("status = ?", true)
end
So, how can I get the count of these two methods in my json output?
I'm using Rails 3.1
The serialization of objects in Rails has two steps:
First, as_json is called to convert the object to a simplified Hash.
Then, to_json is called on the as_json return value to get the final JSON string.
You generally want to leave to_json alone so all you need to do is add your own as_json implementation sort of like this:
def as_json(options = { })
# just in case someone says as_json(nil) and bypasses
# our default...
super((options || { }).merge({
:methods => [:finished_items, :unfinished_items]
}))
end
You could also do it like this:
def as_json(options = { })
h = super(options)
h[:finished] = finished_items
h[:unfinished] = unfinished_items
h
end
if you wanted to use different names for the method-backed values.
If you care about XML and JSON, have a look at serializable_hash.
With Rails 4, you can do the following -
render json: #my_object.to_json(:methods => [:finished_items, :unfinished_items])
Hope this helps somebody who is on the later / latest version
Another way to do this is add this to your model:
def attributes
super.merge({'unfinished' => unfinished_items, 'finished' => finished_items})
end
This would also automatically work for xml serialization.
http://api.rubyonrails.org/classes/ActiveModel/Serialization.html
Be aware though, you might want use strings for the keys, since the method can not deal with symbols when sorting the keys in rails 3. But it is not sorted in rails 4, so there shouldn't be a problem anymore.
just close all of your data into one hash, like
render json: {items: items, finished: finished, unfinished: unfinished}
I just thought I'd provide this answer for anyone like myself, who was trying to integrate this into an existing as_json block:
def as_json(options={})
super(:only => [:id, :longitude, :latitude],
:include => {
:users => {:only => [:id]}
}
).merge({:premium => premium?})
Just tack .merge({}) on to the end of your super()
This will do, without having to do some ugly overridings. If you got a model List for example, you can put this in your controller:
render json: list.attributes.merge({
finished_items: list.finished_items,
unfinished_items: list.unfinished_items
})
As Aswin listed above, :methods will enable you to return a specific model's method/function as a json attribute, in case you have complex assosiations this will do the trick since it will add functions to the existing model/assossiations :D it will work like a charm if you dont want to redefine as_json
Check this code, and please notice how i'm using :methods as well as :include [N+Query is not even an option ;)]
render json: #YOUR_MODEL.to_json(:methods => [:method_1, :method_2], :include => [:company, :surveys, :customer => {:include => [:user]}])
Overwritting as_json function will be way harder in this scenario (specially because you have to add the :include assossiations manually :/
def as_json(options = { })
end
If you want to render an array of objects with their virtual attributes, you can use
render json: many_users.as_json(methods: [:first_name, :last_name])
where first_name and last_name are virtual attributes defined on your model
i am changing from WebORB to Warhammerkids Rails3-amf - great choice, even tehre are some open issues. One those issues is, how can I get records from an association to the array, which is sent back to the Flex-Application.
In WebORB the method of the controller looks like:
def getClients(clientFilter,myMandant)
clients = Client.find(:all, :conditions => {:account_id => myMandant}, :include => [:addresses, :contacts, :proofs])
end
in Rails3-amf I have a similar construction:
def getClients()
#clients = Client.find(:all, :conditions => {:account_id => params[1]}, :include => [:addresses, :contacts, :proofs])
respond_with(#clients) do |format|
format.amf { render :amf => #clients}
end
With this code I get back all correctly typed Client Objects as an Array, but without the records from the ":include" argument.
How can I handle this ??
I also tried another way with:
....
respond_with(#clients) do |format|
format.amf { render :amf => #clients.to_amf(:include => [:addresses, :contacts, :proofs])}
....
With this try I got an error message" undefined method to_amf for #.
Thanks for any help.
I don't know about rail3-amf, but you might find it worthwhile having a look at restfulx - https://github.com/dima/restfulx/tree/rails3
It consists of a library for rails, and a library for flex. It supports data transfer through json, xml, or amf.
The actionscript api for working with models is very nice too:
var user:User = new User();
user.first_name = "Ed";
user.create();
It can also keep track of rails associations etc.:
trace(user.account.title);
See more usage here - https://github.com/dima/restfulx_framework/wiki/Working-with-RestfulX-Models
Say i have this short code:
item = Item.find(params[:id])
render :json => item.to_json
but i needed to insert/push extra information to the returned json object, how do i do that?
Lets say i need to insert this extra info:
message : "it works"
Thanks.
item = Item.find(params[:id])
item["message"] = "it works"
render :json => item.to_json
The to_json method takes an option object as parameter . So what you can do is make a method in your item class called as message and have it return the text that you want as its value .
class Item < ActiveRecord::Base
def message
"it works"
end
end
render :json => item.to_json(:methods => :message)
I found the accepted answer now throws deprecation warnings in Rails 3.2.13.
DEPRECATION WARNING: You're trying to create an attribute message'. Writing arbitrary attributes on a model is deprecated. Please just useattr_writer` etc.
Assuming you don't want to put the suggested attr_writer in your model, you can use the as_json method (returns a Hash) to tweak your JSON response object.
item = Item.find(params[:id])
render :json => item.as_json.merge(:message => 'it works')
How to append data to json in ruby/rails 5
If you use scaffold, e.g.:
rails generate scaffold MyItem
in the view folder you will see next files:
app/view/my_item/_my_item.json.jbuilder
app/view/my_item/index.json.jbuilder
so, you can add custom data to json output for an item, just add this:
json.extract! my_item, :id, :some_filed, :created_at, :updated_at
json.url my_item_url(my_item, format: :json)
json.my_data my_function(my_item)
As you can see, it's possible to modify as one item json output, as index json output.
I always use:
#item = Item.find(params[:id])
render json: { item: #item.map { |p| { id: p.id, name: p.name } }, message: "it works" }
Have you tried this ?
item = Item.find(params[:id])
item <<{ :status => "Success" }
render :json => item.to_json