Ruby on Rails api json response methods and include - ruby-on-rails

I have technical question i have user model which have many questions so in my API controller i do:
render json: user, :include => {
:questions => {
:only => [:text]
},
}
Now i want to add to JSON response question count. How can i do that in best way. I know that i can create method inside model : count_question and after that do:
render json: user, :include => {
:questions => {
:only => [:text]
},
}, :methods => [
:count_question
]
and my response will be good. But is there better way to put that information to JSON. I want to avoid add method inside model. Is it possible to determine this count inside json renderer?
Greetings

Checkout JSON API and Active Model Serializers with Rails
This will hep you out format your JSON.

Related

Rails custom field in to_json

I have this code:
#locations = #list.all.to_json(:root => false,
:only => [:author,:lat,:long,:text])
It works great and is exactly what I want. But now I want to add to the fields an url for the show page of each entry.
So it would be like:
[{
"author:"blabla",
"lat":blabla,
"long":blabla,
"text":"blabla",
"url":"url for show page"
}]
I've tried merge option but it doesn't add anything or throw any errors.
How can I achieve this?
Why don't you add this field to a model (or whatever you called #list.all). For example:
class MyModel
def url
"/url/to/page/#{id}"
end
end
and in the controller just add a field to only array
#locations = #list.all.to_json(:root => false,
:only => [:author,:lat,:long,:text,:url])
Thanks to phts I was able to get a solution:
I defined the method in the model
def url
"/url/to/page/#{id}"
end
And in the options i added:
:methods => :url
And it worked.
You have an array after to_json, so there is no #merge:
#locations = #list.all.to_json(:root => false, :only => [:author,:lat,:long,:text]).collect do |l|
l.merge( {text: 'mytext',url: 'myurl'} )
end

Add generic fields to rails JSON response

I need to render JSON response for the REST api in which I need to include additional JSON fields that are not part of the model being rendered. I did read this link about ActiveModel where it suggests to use the ":methods" option to call another method where I can additional generic fields.
def add_fields
{ "field1" => "true" }
end
if #user.save
render :json => #user.as_json(:only => [:username, :org], :methods => [:add_fields])
endif
However, when the JSON response is received only the username & org fields are returned in the JSON. The additional parameters defined in the method "add_fields" are not added. What is it that I am missing due to which additional fields are not being added to JSON response. Or is there a better way to add generic fields (not part of model) in JSON response?
May be you forgot to implement the #add_fields method in User.
I just tested this on my own User model and it worked just fine.
2.0.0p195 :002 > user.as_json(only: [:first_name, :last_name], methods: [:full_name])
{
"first_name" => "Cody",
"last_name" => "Russell",
:full_name => "Cody Russell"
}
Are you sure you have the method defined, and it's a public method?

Rails - Paperclip url in JSON along with other attributes

I've followed Ryan Bates' screencast on using jQuery Tokeninput for an auto-completing list for a many-to-many association. Now I want to pull in a photo for each result. I'm using Paperclip and get the url's passed into a JSON file by doing this in the controller:
format.json { render :json => #users.map(&:photo_url) }
Ryan's code for passing the attributes into a JSON file is this:
format.json { render :json => #users.map(&:attributes) }
But how can I combine the two to display both the :attributes and :photo_url methods in the JSON file?
I've tried different things, including the below code, but nothing seems to work. It seems as if there can only be one method called on .map?
// Doesn't work
format.json { render :json => #users.map(&:attributes, &:photo_url) }
// Doesn't work
format.json { render :json => #users.map(&:attributes).map(&:photo_url) }
Does this help? (Note - I'm just returning from a night out and am not 100%, so I might be misunderstanding your question entirely.)
This creates an array of arrays: The first element in the array contains the user's attributes, and the second contains the photo URL:
#users.map {|u| [u.attributes, u.photo_url]}
This creates a hash - just like the above array. But the first element is named "attributes" and the second is named "photo_url".
#users.map {|u| {:attributes => u.attributes, :photo_url => u.photo_url}}
Try plugging one or both of those in. They should work for you.
(E.g. format.json { render :json => #users.map {|u| [u.attributes, u.photo_url]} }).
Edit:
Just had another thought.
You can merge the two into one collection (so that you'll have it all in one hash instead of separate elements in an array):
#users.map {|u| u.attributes.merge(:photo_url => u.photo_url)}
That'll add photo_url as a key to the attributes hash. It might work more easily for whatever code you've written to read the JSON.
In case of this being helpful to anyone, i find out a nice way to do this:
class MyModel < ActiveRecord::Base
has_attached_file :avatar, :styles => { :large => "500x500#", :medium => "300x300#", :small => "100x100#", :thumb => "50x50#" }
def as_json(options)
json = super
self.avatar.styles.each do | format |
json = json.merge({"avatar_"+format[0].to_s => self.avatar(format[0])})
end
json
end
end
You can then simply call
render :json => #my_model
Also working while rendering collections.
It is then possible to do some conditional rendering with as_json(options), with something like:
model_to_json = #my_model.to_json(:nested => true)
render :json => model_json

RAILS3: to_JSON with multiple objects and includes

def list
#rings = Ring.order("RAND()")
#JSON RENDERING
render :json => #rings.to_json(:include => [:variations, :stones]), :callback => params[:callback]
end
def show
#showring = Ring.includes(:stones, :variations).find(params[:id])
#other_rings = Ring.select([:id, :stone_count]).where(:style_number => #showring.style_number).reject{ |ring| ring == #showring}
#JSON RENDERING
render :json => {#showring.to_json(:include =>[:variations, :stones]), :other_rings => #other_rings}, :callback => params[:callback]
end
My list view rendering works fine, but when i want to do a show view, with two objects, and showring with includes won't render proper JSON. It is quoting everything in the object with the includes...
JSON output looks like this:
showring => "{"available":"yes","eng...9","stone_y":"149.4"}]}"
other_rings => properly rendered object
On a seperate note, if i have already added the includes to #rings object, why do i then again have to add the association in the "to_json" method?
When you do
render :json => {:show_ring => #showring.to_json(:include =>[:variations, :stones]), :other_rings => #other_rings}
Rails is converting #showring to json (ie getting back a string representation), i.e. the value is the string literal. Instead do
render :json => {:show_ring => #showring.as_json(:include =>[:variations, :stones]), :other_rings => #other_rings}
as_json does all the work of turning the object into a hash but without the final step of turning into a string
if you are going to invest more time in building more JSON objects, you should look into a gem called rabl. It makes building JSON very simple, good for customization which then is good for building API.

Filter a model's attributes before outputting as json

I need to output my model as json and everything is going fine. However, some of the attributes need to be 'beautified' by filtering them through some helper methods, such as number_to_human_size. How would I go about doing this?
In other words, say that I have an attribute named bytes and I want to pass it through number_to_human_size and have that result be output to json.
I would also like to 'trim' what gets output as json if that's possible, since I only need some of the attributes. Is this possible? Can someone please give me an example? I would really appreciate it.
Preliminary search results hint at something regarding as_json, but I can't find a tangible example pertaining to my situation. If this is really the solution, I would really appreciate an example.
Research: It seems I can use to_json's options to explicitly state which attributes I want, but I'm still in need of figuring out how to 'beautify' or 'filter' certain attributes by passing them through a helper before they're output as json.
Would I create a partial for a single json model, so _model.json.erb, and then create another one for the action I'm using, and within that simply render the partial with the collection of objects? Seems like a bunch of hoops to jump through. I'm wondering if there's a more direct/raw way of altering the json representation of a model.
Your model can override the as_json method, which Rails uses when rendering json:
# class.rb
include ActionView::Helpers::NumberHelper
class Item < ActiveRecord::Base
def as_json(options={})
{ :state => state, # just use the attribute when no helper is needed
:downloaded => number_to_human_size(downloaded)
}
end
end
Now you can call render :json in the controller:
#items = Item.all
# ... etc ...
format.json { render :json => #items }
Rails will call Item.as_json for each member of #items and return a JSON-encoded array.
I figured out a solution to this problem, but I don't know if it's the best. I would appreciate insight.
#items = Item.all
#response = []
#items.each do |item|
#response << {
:state => item.state,
:lock_status => item.lock_status,
:downloaded => ActionController::Base.helpers.number_to_human_size(item.downloaded),
:uploaded => ActionController::Base.helpers.number_to_human_size(item.uploaded),
:percent_complete => item.percent_complete,
:down_rate => ActionController::Base.helpers.number_to_human_size(item.down_rate),
:up_rate => ActionController::Base.helpers.number_to_human_size(item.up_rate),
:eta => item.eta
}
end
respond_to do |format|
format.json { render :json => #response }
end
Basically I construct a hash on the fly with the values I want and then render that instead. It's working, but like I said, I'm not sure if it's the best way.

Resources