I created a REST API with rails and use .to_json to convert the API output to json response. E.g. #response.to_json
The objects in the response contains created_at and updated_at fields, both Datetime
In the JSON response, both these fields are converted to strings, which is more costly to parse than unixtimestamp.
Is there a simple way I can convert the created_at and updated_at fields into unixtimestamp without having to iterate through the whole list of objects in #response?
[updated]
danielM's solution works if it's a simple .to_json. However, if I have a .to_json(:include=>:object2), the as_json will cause the response to not include object2. Any suggestions on this?
Define an as_json method on your response model. This gets run whenever you return an instance of the response model as JSON to your REST API.
def as_json(options={})
{
:id => self.id,
:created_at => self.created_at.to_time.to_i,
:updated_at => self.updated_at.to_time.to_i
}
end
You can also call the as_json method explicitly to get the hash back and use it elsewhere.
hash = #response.as_json
Unix timestamp reference: Ruby/Rails: converting a Date to a UNIX timestamp
Edit: The as_json method works for relationships as well. Simply add the key to the hash returned by the function. This will run the as_json method of the associated model as well.
def as_json(options={})
{
:id => self.id,
:created_at => self.created_at.to_time.to_i,
:updated_at => self.updated_at.to_time.to_i,
:object2 => self.object2,
:posts => self.posts
}
end
Furthermore, you can pass in parameters to the method to control how the hash is built.
def as_json(options={})
json = {
:id => self.id,
:created_at => self.created_at.to_time.to_i,
:updated_at => self.updated_at.to_time.to_i,
:object2 => self.object2
}
if options[:posts] == true
json[:posts] = self.posts
end
json
end
hash = #response.as_json({:posts => true})
Related
I've got a hash and I've found that with net/http posting I have to convert it into a flat format.
Example
invoice = { :no => "100", :date => "08/08/2022", :client => {:name => "Foo" } }
Would become
params = { "invoice[no]" => "100", "invoice[date]" => "08/08/2022", "invoice[client][name]" => "Foo" }
Is there a way to do this automatically? I've tried to_param & to_query, flatten and encode_www_form but they don't convert it to this required format.
The post action I'm doing is to a Ruby On Rails backend which I use Devise Tokens to authorise.
res = Net::HTTP.post_form(uri, params)
You need CGI.parse method. It parses an HTTP query string into a hash of key => value pairs
CGI.parse({ invoice: invoice }.to_query)
# => {"invoice[client][name]"=>["Foo"], "invoice[date]"=>["08/08/2022"], "invoice[no]"=>["100"]
Don't care about single-element arrays as values. It will works well
params = CGI.parse({ invoice: invoice }.to_query)
res = Net::HTTP.post_form(uri, params)
I think this snippet should do the job:
invoice = { :no => "100", :date => "08/08/2022", :client => {:name => "Foo" } }
CGI.unescape({invoice:}.to_query)
.split('&')
.map{ |p| p.split('=') }
.to_h
{"invoice[client][name]"=>"Foo", "invoice[date]"=>"08/08/2022", "invoice[no]"=>"100"}
First of all, we let ActiveRecord generate the query-like structure from a hash using the method to_query. We need to unescape the query string afterward since we don't want to have URL-encoded output there. After that we split the string by parameter using split('&') and every parameter into key-value using split('='). Finally, we convert the output back into a hash.
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
In a rails app I have an action that returns a json representation of a collection of different models. It looks something like this:
respond_to :json
def index
#cars = Car.all
#vans = Van.all
respond_with({
:cars => #cars,
:vans => #vans
})
end
However, I want to customise the attributes and methods that are passed to the json object. A bit like:
respond_with({
:cars => #cars.to_json(:only => [:make, :model], :methods => [:full_name]),
:vans => #vans
})
Doing the above, causes the json representation of the "cars" to be escaped as one big string, like:
{
"cars":"[{\"car\":{\"make\":\"Ford\" ... etc
"vans": [{"van":{"make":"Citreon" ... vans not escaped
}
Obviously I'm approaching this the wrong way. Can anyone point me in the right direction?
Since you're nesting the to_json in another Hash, I think you need to use as_json (which returns a Hash instead of a String) instead:
respond_with({
:cars => #cars.as_json(:only => [:make, :model], :methods => [:full_name]),
:vans => #vans
})
I'm able to create and send a JSON object like so:
#mylist << {
:id => item.id,
:name => name.id
}
render :json => { :result => 'success', :mylist => #mylist }
That works great. Problem I'm having now is that I need to include users with are 1 or more per item.
#mylist << {
:id => item.id,
:name => name.id,
:users => item.users
}
Where item.users contains a list of (user.id, user.name, user.desc).
how do I include an array like users inside a json object? How to build in Rails and then how to parse it with jQuery?
Thanks
UPDATE
the code above is inside a:
#items.each_with_index do |item, i|
end
Perhaps that is a problem here?
If items.users is an array then it will be rendered as a JSON array.
When you get the JSON response in your JavaScript, you'll just need to loop over the array:
for (var i = 0; i < data.users.length; i++) {
//do something with data.users[i]
}
where data is the JSON data returned from the Ajax call.
This should work fine out of the box. Arrays inside arrays is no problem. Rails walks down your objects and tries to convert them to simple objects like hashes, arrays, strings and numbers. ActiveRecord objects will turn all their attributes into a hash when you convert it to JSON. If item.users is an array of ActiveRecord instances, than your example will automatically work. You can retrieve them in Javascript exactly as you would walk through a hash and array in Ruby. So something like:
response['mylist']['users'][0]['name']
Edit
To limit the fields in the user, add a method to the user class:
class User < ActiveRecord::Base
def as_json(*)
{ :name => name, :desc => desc }
end
end
When using :methods in to_json, is there a way to rename the key? I'm trying to replace the real id with a base62 version of it, and I want the value of base62_id to have the key :id.
#obj.to_json(
:except => :id
:methods => :base62_id
)
I tried to do
#obj.to_json(
:except => :id
:methods => { :id => :base62_id }
)
but that didn't work.
Any advice?
The to_json serializer uses the name of the method as the key for serialization. So you can't use the methods option for this.
Unfortunately to_json method doesnt acceptblock` parameter, otherwise you could have done something similar to
#obj.to_json(:except => :id) {|json| json.id = base62_id }
So that leaves us with a ugly hack such as:
def to_json(options={})
oid, self.id = self.id, self.base62_id(self.id)
super
ensure
self.id = oid
end
Now to_json will return the expected result.