My Controller Returns the #quotes, which can be an empty array.
I then render the following view.
json.set! :quotes do
#quotes.each do |quote|
json.set! quote.id do
json.id quote.id
json.symbol quote.symbol
json.price quote.price
json.datetime quote.datetime
end
end
end
This typically results in
"quotes": {
"123": {
"id": 123,
"symbol": "AAPL",
"price": 100,
"datetime": "2019-12-31T16:00:00.000Z"
},
However if #quotes is an empty array the response is
{}
When I want
{
"quotes": {}
}
Is there a way to achieve my goal without an explicit check if the object is empty?
I Do Not want todo
if #quotes.empty?
json.quotes({})
else
-- I am using Jbuilder 2.7
I advice you to use jb gem. It is simple to move from jbuilder to jb (i was doing it on production project). Jb few time faster than jbuilder and do not have ugly jbuilder syntax, it is just plain ruby.
Your problem can be solved in jb
json = { quotes: {} }
#quotes.each do |quote|
json[:quotes][quote.id] = {
...
}
end
json
Related
I have following array of hash. I am trying to loop over it and build an array of hash of values of id and product_order_id.
objects =
[
#<Product: 0x00007ffd4a561108
#id="1",
#product_id="2",
#product_order_id="23",
#description="abc",
#status="abcde",
#start_date=nil,
#end_date=nil>,
#<Product: 0x00007ffd4a560c80
#id="45",
#product_id="22",
#product_order_id="87",
#description="ahef",
#status="gesff",
#start_date=nil,
#end_date=nil>
......more objects.....
]
This is what it should look like
[{ "1": "23" }, { "45": "87" }] -->its going to be uuid
I tried doing this but no luck
def mapped_product(objects)
mapping = []
objects.each do |object|
mapping << {
object.product_order_id: object.id
}
end
end
Any idea?
inline solution:
> Hash[objects.map{|p| [p.id, p.product_order_id] }]
# Output : [{ 1=>23 }, { 45=>87 }]
I'd usually implement it using an each_with_object
objects.each_with_object({}) { |obj, acc| acc[obj.id] = obj.product_order_id }
Unless I reaaaly want to squeeze some performance, than I'd go with Gagan's answer
Have you tried this?
def mapped_product(objects)
mapping = []
objects.each do |object|
mapping << {
object.id => object.product_order_id # I'm using an `=>` here
}
end
mapping # return the new mapping
end
I've just changed the : on the hash for a => to "make it dynamic" and swapped the values of id and product_order_id
You can also use a map here:
def mapped_product(objects)
objects.map do |object|
{ object.id => object.product_order_id }
end
end
I am creating API. Using ActiveRecords. Problem I am getting
Multiple array object of country, all I want one array containing all location
Current Output
{
"id": "180a096",
"country": [
{
"location": "US"
},
{
"location": "CH"
}
]
}
Expected Output
{
"id": "180a096",
"country": [
{"location":["US","CH"]}
]
}
Code
def as_json(options={})
super(:only => [:id ],:include => { :country => { :only => :location } })
end
Can anyone help me to restructured the object as in expected output.
If your hash is called hash you can do:
hash[:country].map {|h| h[:location]}
If you have to access attributes on associated models you can do:
countries.pluck(:location)
Unrelated to the question, but when I have to manage country info in my app I tend to use the countries gem. https://github.com/hexorx/countries
It has all kinds of useful helper methods, and it prevents you from having to maintain standardized country information.
You can simply map all the location and assign it to hash[:country]
2.4.0 :044 > hash[:country].map! { |c| c[:location] }
=> ["US", "CH"]
2.4.0 :045 > hash
=> {:id=>"180a096", :country=>["US", "CH"]}
As mentioned in my comment, you can do in one line like
actual_hash[:country].map! { |country| country[:location]}
actual_hash # => {:id=>"180a096", :country=>["US", "CH"]}
The output is clean but not as expected.
Or, a bit more lines to get the exact output:
location_array = [{location: []}]
actual_hash[:country].each { |country| location_array[0][:location] << country[:location]}
actual_hash[:country] = location_array
actual_hash # => {:id=>"180a096", :country=>[{:location=>["US", "CH"]}]}
def rearrange_json(input)
input_hash = JSON.parse(input)
output_hash = input_hash.clone
output_hash[:country] = {location: []}
input_hash[:country].map {|l| output_hash[:country][:location] << l[:location] }
output_hash.as_json
end
With this method, you can convert your json to a hash, then rearrange its content they way you want by adding the country codes as values for the [:country][:location] key of the output hash, and end up with some properly formatted json. It's not a one-liner, and probably not the most elegant way to do it, but it should work.
I'm trying to get a JSON output using jbuilder that looks like this:
[{"correct_response": 0,
"section_id": 1,
"image_url": "https://850.s3.amazonaws.com/uploads/question/3/PreguntaWeb.png?AWSAccessKeyId=AKIAJSKXBBJ4URBWSPUQ&Signature=r8COJLWNWABfwlm6BQ4ZpPlvFGw%3D&Expires=1401509509",
"responses": [{
"responseA": "https://850.s3.amazonaws.com/uploads/response/1/alternativaA.png?AWSAccessKeyId=AKIAJSKXBBJ4URBWSPUQ&Signature=MkUUT7NuoHDH/BjiJdMiHV5f%2BB4%3D&Expires=1401509509"},
{"responseB": "https://850.s3.amazonaws.com/uploads/response/2/alternativaB.png?AWSAccessKeyId=AKIAJSKXBBJ4URBWSPUQ&Signature=EZ6KeqhzlwPGX1PAetvqR/GPH2M%3D&Expires=1401509509"},
{"responseC": "https://850.s3.amazonaws.com/uploads/response/3/alternativaC.png?AWSAccessKeyId=AKIAJSKXBBJ4URBWSPUQ&Signature=/Ntt6y4JrfVjjw0zpOKgIXtihvI%3D&Expires=1401509509"},
{"responseD": "https://850.s3.amazonaws.com/uploads/response/4/alternativaD.png?AWSAccessKeyId=AKIAJSKXBBJ4URBWSPUQ&Signature=Exrr0WTsSx2n3FixjwADiiwwPjM%3D&Expires=1401509509"},
{"responseAE": "https://850.s3.amazonaws.com/uploads/response/5/alternativaE.png?AWSAccessKeyId=AKIAJSKXBBJ4URBWSPUQ&Signature=udJ5jK/zG9ug8A6WwsnhYZRcsPk%3D&Expires=1401509509"}
]
}]
I think I'm close, but I don't know what is wrong with my code:
json.array!(#questions) do |json, question|
json.extract! question, :correct_response, :section_id, :image_url
json.responses question.responses do |response|
[ 'responseA', 'responseB', 'responseC', 'responseD', 'responseE' ].each { |letter|
response.set!(letter, response.image_url )
}
end
end
end
Someone has any suggestions?
I'm using active_model_serializer. Now I want to serialize an object with pagination, should I do the pagination logic in the controller or in the serializer?
If I choose to do the pagination in serializer, I need to pass the page_number and per_page to the serializer. How should I do that? My understanding is serializer only takes the model object.
Single Use Solution
Regular serializers are only concerned with single items - not paginated lists. The most straight forward way to add pagination is in the controller:
customers = Customer.page(params[:page])
respond_with customers, meta: {
current_page: customers.current_page,
next_page: customers.next_page,
prev_page: customers.prev_page,
total_pages: customers.total_pages,
total_count: customers.total_count
}
Reusable Solution
However, this is pretty tedious if you need pagination logic for multiple objects. Looking through the documentation for active_model_serializers you'll come across an ArraySerializer for serializing an array of objects. What I did was create pagination_serializer.rb using ArraySerializer to automatically add the meta tag for paginated arrays:
# my_app/app/serializers/pagination_serializer.rb
class PaginationSerializer < ActiveModel::Serializer::ArraySerializer
def initialize(object, options={})
meta_key = options[:meta_key] || :meta
options[meta_key] ||= {}
options[meta_key][:pagination] = {
current_page: object.current_page,
next_page: object.next_page,
prev_page: object.prev_page,
total_pages: object.total_pages,
total_count: object.total_count
}
super(object, options)
end
end
Once you have PaginationSerializer added to your rails app, you simple need to call it when you need pagination meta tags from your controller:
customers = Customer.page(params[:page])
respond_with customers, serializer: PaginationSerializer
Note: I wrote this to use Kaminari as the paginator. However, it can easily be modified to work with any pagination gem or custom solution.
2020 update: active_model_serializer now supports this out of the box if you use json_api schema, but the docs also teach you how to add it if you use the json schema.
The docs are here: https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/howto/add_pagination_links.md
Below I explain how to achieve the desired results if you are using the json_api or the json adapters. Check which one you're using on ActiveModelSerializers.config.adapter.
If you are using the JSON API adapter (your ActiveModelSerializers.config.adapter = :json_api)
Pagination links will be included in your response automatically as long as
the resource is paginated and if you are using the JsonApi adapter.
If you want pagination links in your response, use Kaminari
or WillPaginate.
Kaminari examples
#array
#posts = Kaminari.paginate_array([1, 2, 3]).page(3).per(1)
render json: #posts
#active_record
#posts = Post.page(3).per(1)
render json: #posts
WillPaginate examples
#array
#posts = [1,2,3].paginate(page: 3, per_page: 1)
render json: #posts
#active_record
#posts = Post.page(3).per_page(1)
render json: #posts
ActiveModelSerializers.config.adapter = :json_api
ex:
{
"data": [
{
"type": "articles",
"id": "3",
"attributes": {
"title": "JSON API paints my bikeshed!",
"body": "The shortest article. Ever.",
"created": "2015-05-22T14:56:29.000Z",
"updated": "2015-05-22T14:56:28.000Z"
}
}
],
"links": {
"self": "http://example.com/articles?page[number]=3&page[size]=1",
"first": "http://example.com/articles?page[number]=1&page[size]=1",
"prev": "http://example.com/articles?page[number]=2&page[size]=1",
"next": "http://example.com/articles?page[number]=4&page[size]=1",
"last": "http://example.com/articles?page[number]=13&page[size]=1"
}
}
ActiveModelSerializers pagination relies on a paginated collection with the methods current_page, total_pages, and size, such as are supported by both Kaminari or WillPaginate.
If you are using the JSON adapter (your ActiveModelSerializers.config.adapter = :json)
If you are not using JSON adapter, pagination links will not be included automatically, but it is possible to do so using meta key.
Add this method to your base API controller.
def pagination_dict(collection)
{
current_page: collection.current_page,
next_page: collection.next_page,
prev_page: collection.prev_page, # use collection.previous_page when using will_paginate
total_pages: collection.total_pages,
total_count: collection.total_count
}
end
Then, use it on your render method.
render json: posts, meta: pagination_dict(posts)
ex.
{
"posts": [
{
"id": 2,
"title": "JSON API paints my bikeshed!",
"body": "The shortest article. Ever."
}
],
"meta": {
"current_page": 3,
"next_page": 4,
"prev_page": 2,
"total_pages": 10,
"total_count": 10
}
}
You can also achieve the same result if you have a helper method that adds the pagination info in the meta tag. For instance, in your action specify a custom serializer.
render json: #posts, each_serializer: PostPreviewSerializer, meta: meta_attributes(#posts)
#expects pagination!
def meta_attributes(collection, extra_meta = {})
{
current_page: collection.current_page,
next_page: collection.next_page,
prev_page: collection.prev_page, # use collection.previous_page when using will_paginate
total_pages: collection.total_pages,
total_count: collection.total_count
}.merge(extra_meta)
end
Attributes adapter
This adapter does not allow us to use meta key, due to that it is not possible to add pagination links.
https://github.com/x1wins/tutorial-rails-rest-api/blob/master/lib/pagination.rb
# /lib/pagination.rb
class Pagination
def self.build_json object, param_page = {}
ob_name = object.name.downcase.pluralize
json = Hash.new
json[ob_name] = ActiveModelSerializers::SerializableResource.new(object.to_a, param_page: param_page)
json[:pagination] = {
current_page: object.current_page,
next_page: object.next_page,
prev_page: object.prev_page,
total_pages: object.total_pages,
total_count: object.total_count
}
return json
end
end
how to use
#app/controller/posts_controller.rb
#post#index
render json: Pagination.build_json(#posts)
full source https://github.com/x1wins/tutorial-rails-rest-api
I have below output from active record query
[{"image_id"=>1, "image_name"=> "image1", action_type"=>"Call", "count"=>2},
`{"image_id"=>1, "image_name"=> "image1","action_type"=>"sms", "count"=>1},
{"image_id"=>2, "image_name"=> "image2","action_type"=>"sms", "count"=>1} ]`
Now I want this to be converted into Json object like below
{ "1": { "counts": { "call": 2, "sms": 1 } , "title":'image1' },
"2": { "counts": {"sms": 1} , 'title':'image2'}}
Please check this code.
#xx = [{"image_id"=>1, "image_name"=>"image1", "action_type"=>"Call", "count"=>2}, {"image_id"=>1, "image_name"=>"image1", "action_type"=>"sms", "count"=>1}, {"image_id"=>1, "image_name"=>"image1", "action_type"=>"sms", "count"=>1}]
#arr = []
#xx.each_with_index do |x, i|
#arr << {(i+1).to_s.to_sym => {"counts" => {x["action_type"].to_sym => x["count"]}}}
end
respond_to do |f|
f.json {render :json => #arr}
end
There are two popular libraries that are both very helpful:
active_model_serializers
jbuilder
I prefer active_model_serializers, personally. Many disagree.