How to create this custom JSON layout using jbuilder - ruby-on-rails

I'm trying to create this JSON layout using jbuilder:
"entities" : {
"users": {
1: {name: 'abs', age: 44},
2: {name: 'arms', age: 12},
3: {name: 'legs', age: 34},
}
}
I have this so far:
json.entities do
json.users #response.users do |user|
json.(user, :id)
end
end
But this is returning:
entities: {
users: [
{
id: 1
}
]
}
I need to make the key in "users" to be the user.id value, and then list the attributes.

This will get you the hash you need. You'll have to call .to_json on it if you need the string.
hash = {users:{}}
#response.users.pluck(:id, :name, :age).each {|u| hash[:users][u[0]] = {name: u[1], age: u[2]}]}
# hash.to_json

Related

Using group_by but return an array of hashes

I simply want to group cities by their state and from there have the array of the hash key (i.e. State Name) return an array of hash data pertaining to it's cities. Right now I have something like this:
City.all.group_by { |c| c.state.name }
Which will return:
{
"Illinois": [# < City id: 3, name: "Chicago", state_id: 3 > ],
"Texas": [# < City id: 2, name: "Houston", state_id: 2 > ],
"California": [# < City id: 1, name: "Los Angeles", state_id: 1 > ],
"New York": [# < City id: 4, name: "New York City", state_id: 4 > ]
}
Notice how it returns an array of rails objects. Instead I want to return an array of hashes with certain attributes, like their id and name.
The reason the grouped values are Rails objects (your models) is due to the fact that you also start with these objects. You can use the attributes method to retrieve the attributes of a model instance as a hash.
The following achieves the result you want:
City.all.group_by { |city| city.state.name }
.transform_values { |cities| cities.map(&:attributes) }
If you only want specific attributes, use slice instead:
City.all.group_by { |city| city.state.name }
.transform_values { |cities| cities.map { |city| city.slice(:id, :name) } }
Note that slice will return an ActiveSupport::HashWithIndifferentAccess instance. Which mostly can be used in the same manner as a normal hash, but returns the same value for both hash[:name] and hash['name']. If you rather use a normal hash append a to_hash call after the slice call.
This should be enough for you
City.all.group_by { |c| c.state.name }.map {|k,v| [k, v.attributes] }.to_h
and to select only specified attributes do
v.attributes.slice(:name, :id)
One of the easiest approach is to convert it into json object
City.all.as_json.group_by { |c| c.state.name }
this will fix the issue

How can i map one query result to another query result in ruby on rails

Hi I am new to Ruby on Rails development. I have two queries with different model. My first_query is get from question model and second query is get from favourite model. I want to map with a column user_favourite from second query result to first query result.
this is my controller queries
def index
#first_query = Question.order('created_at DESC').page(params[:page]).per( (ENV['ILM_QUESTIONS_PER_PAGE'] || 5).to_i )
#second_query=Favourite.with_user_favourite(#user)
#combined_queries = #first_query + #second_query
end
favourite.rb
scope :with_user_favourite, -> (user) {
joins(:user).
where(favourites: {user_id: user})
}
index.json.builder
json.questions #combined_events
json for the result is
{
questions: [ #this is first query result
{
id: 88,
user_id: 28,
content: "test32",
image: {
url: null,
thumb: {
url: null
},
mobile: {
url: null
}
}
},
{
id: 87,
user_id: 18,
content: "testing riyas",
image: {
url: null,
thumb: {
url: null
},
mobile: {
url: null
}
}
},
{ #this is second query result
id: 1,
user_id: 2,
question_id: 84,
created_at: "2016-05-12T06:51:54.555-04:00",
updated_at: "2016-05-12T06:51:54.555-04:00"
},
{
id: 2,
user_id: 2,
question_id: 81,
created_at: "2016-05-12T07:23:47.770-04:00",
updated_at: "2016-05-12T07:23:47.770-04:00"
}
]
}
i want response like
{
questions: [
{ #first query result
id: 88,
user_id: 28,
content: "test32",
image: {
url: null,
thumb: {
url: null
},
mobile: {
url: null
}
},
user_favorite: { #corresponding result from second query result
id: 1,
user_id: 2,
question_id: 88
}
},
{ #first query result
id: 87,
user_id: 18,
content: "testing riyas",
image: {
url: null,
thumb: {
url: null
},
mobile: {
url: null
}
},
user_favorite: {} #corresponding result from second query result if there is no result for particular question in favourite table
},
]
}
The model relationships are:
class Question
belongs_to :user
has_many :favourite
end
class Favourite
belongs_to :user
belongs_to :question
end
class User
has_many :questions
has_many :favourite
end
You should modify your jBuilder template to support nesting.Since your model association is like one question has_many favorite so it will be an array and you can easily nest one object inside another.
json.array! #questions do |question|
json.user_id question.user_id
json.content question.content
json.user_favorites question.favorites do |json,favorite|
json.id question.favorite.id
json.user_id question.favorite.user.id
json.question_id question.id
end
end
Here is a link that you can refer to for more clarity.
Generate a nested JSON array in JBuilder
Using JBuilder to create nested JSON output in rails
Hope it helps!.
You can add an association between user_favourite and question so that you can select all user favourites on one question.
Question.rb:
has_many :user_favourites
UserFavourite.rb:
belongs_to :question
Then, as your web action:
def index
#questions = Question.all.order('created_at DESC').page(params[:page]).per((ENV['ILM_QUESTIONS_PER_PAGE'] || 5).to_i)
end
And finally, in index.json.builder:
json.questions #questions do |question|
json.user_favourites question.user_favourites
end
including whatever other fields you want.

Array of hashes where hash is returned by a function in jbuilder

I have a PORO TutorProfileHandler that has a function json that returns a hash.
class TutorProfileHandler
def initialize(opts)
#profile = opts[:tutor_profile]
end
def json
tutor = #profile.tutor
return {
id: tutor.id,
first_name: tutor.first_name,
last_name: tutor.last_name.first + '.',
school: #profile.school,
avatar: #profile.avatar.url,
bio: #profile.bio,
academic_level: #profile.academic_level,
headline: #profile.headline,
major: #profile.major,
rate: #profile.rate,
rating: #profile.rating,
courses: JSON.parse(#profile.courses),
video_url: #profile.video_url
}
end
end
In my index_tutor_profiles.json.jbuilder, I would like to generate
{
tutor_profile: [{id: 1, ...}, {id: 2, ...}, ...],
tutor_sum: 20
}
However when I do this
json.tutor_profiles (#tutor_profiles) do |profile|
TutorProfileHandler.new({tutor_profile: profile}).json
end
json.tutor_sum #tutor_sum
It gives me an empty array for tutor_profiles.
However if I move everything from TutorProfileHandler.json to the jbuilder file, it works. How do I explicitly include the hash returned by TutorProfileHandler.json in the jbuilder array?
Note: This returns an array, but it creates a new key-value pair array:
json.tutor_profiles json.array(#tutor_profiles) do |profile|
TutorProfileHandler.new({tutor_profile: profile}).json
end
Result:
{
array: [{id: 1, ...}, {id: 2, ...}, ...],
tutor_profile: [],
tutor_sum: 20
}
There is a ugly approach:
json.tutor_profiles #tutor_profiles do |profile|
tmp_json = TutorProfileHandler.new({tutor_profile: profile}).json
json.(tmp_json, *(tmp_json.keys))
end
I think the best practise is directly nesting inside model. You can get more information from the its github page.

Rails: Prevent JSON hash from being re-ordered

I have the following method in my ContactsController:
def index
#contacts = {}
company = current_employer.company
groups = company.groups.pluck(:name)
contacts = company.contacts.order(last_name: :asc)
contacts.each do |contact|
#contacts[contact.id] = contact.as_json
#contacts[contact.id][:groups] = {}
contact_groups = contact.groups.pluck(:name)
groups.each do |name|
#contacts[contact.id][:groups][name] = contact_groups.include?(name)
end
end
binding.pry
respond_to do |format|
format.json { render json: { contacts: #contacts } }
end
end
This generates a #contacts hash where the keys are the contact IDs, but the hash is ordered by the contact's last name. If I put in a binding.pry I can see that the hash is in the right order that my client expects, however once it's rendered, the hash is reordered by contact IDs.
Here is a sample of what #contacts looks like:
{
contacts: {
24: {
id: 24,
first_name: "Foo",
last_name: "Bar",
email: "foo#foo.co",
groups: {
Test: true,
Random: false,
Example: false
}
},
31: {
id: 31,
first_name: "Jackie",
last_name: "Chan",
email: "chan#example.com",
groups: {
Test: false,
Random: true,
Example: false
}
},
28: {
id: 28,
first_name: "Jason",
last_name: "Rebourne",
email: "jason.rebourne#example.com",
groups: {
Test: false,
Random: false,
Example: true
}
}
}
}
Normally this is desirable behavior, however in this case I'd like to disable it so as to avoid additional iteration through the response on the client side. How can I prevent the hash from being reordered?
Note: I don't have a custom serializer for my Contact or Group classes.
Edit
This is not a duplicate question. I want to return a custom JSON structure, not a standard serialized structure, which is what the linked "duplicate" question is about.

How to group-by and nest results in each group?

I tried to do this:
Things.order("name").group("category_name")
I was expecting the results to be something like this:
[
{
"category_name": "Cat1",
"things":
[
{ "name": "Cat1_Thing1" },
{ "name": "Cat1_Thing1" }
]
},
{
"category_name": "Cat2",
"things":
[
{ "name": "Cat2_Thing3" },
{ "name": "Cat2_Thing4" }
]
}
]
So I would have expected to get an array of "categories" each with an array of "items" which are within that category. Instead, it appears to give me a list of things, sorted by the field I grouped on.
Note: category_name is a column in the thing table.
Try something like
my_grouping = Category.includes(:things).
select("*").
group('categories.id, things.id').
order('name')
=> #<ActiveRecord::Relation [#<Category id: 1, name: "Oranges">, #<Category id: 2, name: "Apples">]>
Though, you'll still have to access the Thing objects via my_grouping.things, they'll already be at your hand, and you won't have to wait for the results. This is likely the sort of interaction you're looking for, vs. mapping them into an actual Array.
One option is to do the grouping in Rails (it returns a hash)
Things.order("name").group_by(&:category_name)
#=> {"cat1" => [thing1,thing2,..], "cat2" => [thing3,thing4,..],..}
ActiveRecord::Base#group performs a SQL GROUP BY. I think, but i'm not sure (depends on your db adapter) that as you don't specify any SELECT clause, you get the first record for each category.
To achieve what you want, there are different ways.
For instance, using #includes :
Category.includes(:things).map do |category|
{
category_name: category.name,
things: things.sort_by(&:name).map{|t| {name: t.name} }
}
end.to_json
Note that the standard (albeit often frowned upon) way to serialize models as json is to use (and override if need be) as_json and to_json. so you would have something along the lines of this :
class Category < ActiveRecord::Base
def as_json( options = {} )
defaults = { only: :name, root: false, include: {links: {only: :name}} }
super( defaults.merge(options) )
end
end
Use it like this :
Category.includes(:links).map(&:to_json)
EDIT
As category_name is only a column, you can do :
Thing.order( :category_name, :name ).sort_by( &:category_name ).map do |category, things|
{ category_name: category, things: things.map{|t| {name: t.name} } }
end.to_json
such thing could belong in the model :
def self.sorted_by_category
order( :category_name, :name ).sort_by( &:category_name ).map do |category, things|
{ category_name: category, things: things.map{|t| {name: t.name} } }
end
end
so you can do :
Thing.sorted_by_category.to_json
this way, you can even scope things further :
Thing.where( foo: :bar ).sorted_by_category.to_json

Resources