Rabl for a nested hash of ActiveRecord collections - ruby-on-rails

I have a few ActiveRecord objects like City, Country, State etc. My controller action is like (as a representative example):
def get_data
#data = {
cities: City.limit(2),
countries: Country.limit(2),
states: State.limit(2),
version: 1000
}
respond_with #data
end
I want to render a JSON like:
{
data: {
country: [{id: 1, name: 'a'}, {id: 2, name: 'b'}],
state: [{id: 1, name: 'p'}, {id: 2, name: 'q'}],
city: [{id: 1, name: 'x'}, {id: 2, name: 'y'}],
version: 1000
}
}
(Please note singular keys for collections, Please do not suggest changes to the JSON structure, as it is fixed.)
What should be the rabl template for this? I have been breaking my head on this for hours.

if i were you i will not even care to go to rabl template for this because what your are creating with this code:
#data = {
cities: City.limit(2),
countries: Country.limit(2),
states: State.limit(2),
version: 1000
}
is the final output , not the individual components.
you can just replace this with:
render :json => {
:data => {
cities: City.limit(2).map{ |c|{:id => c[:id] },
countries: Country.limit(2).map{ |c|{:id => c[:id] },
states: State.limit(2).map{ |c|{:id => c[:id] },
version: 1000
}
}
its not a best use case for rabl , but if you still want it to load via rabl this below code in the get_data.json.rabl will do just fine.
node :data do
{
:version_number => #data[:version_number],
:country => #data[:countries].map{ |c|{:id => c[:id] , :name => c[:name]} }
:state => #data[:states].map{ |s|{:id => s[:id] , :name => s[:name]} }
:city => #data[:city].map{ |c|{:id => c[:id] , :name => c[:name]} }
}
end

Related

Ruby how to format nested hash

I have two queries I am running and iterating over both and my final hash is seen below. But, I want to have format on how the data is being stored in the hash that I'm creating or format it after I'm done creating it. But I am not sure how to achieve the desired format where the names fall under the same id as show below
desired format of example data:
[
{
id: 1,
accepted: false,
trans: 10234
names: [
{ name: "Joe", amount: "$1,698.00" },
{ name: "Smith", amount: "$674.24" },
]
},
{
id: 2,
accepted: true,
trans: 10234,
names: [
{ name: "Joe", amount: "$1,698.00" },
{ name: "Smith", amount: "$674.24" },
]
}
]
current format I have
[
{
:id => 1,
:accepted => false,
:trans => 8,
:name => "Smith",
:amount => 36.0
},
{
:id => 1,
:amount => false,
:trans => 8,
:name => "Joe",
:amount => 6.0
},
{
:id => 3,
:accepted => false,
:trans => 8,
:name => "Tom",
:amount => 34.0
},
{
:id => 3,
:accepted => false,
:trans=> 8,
:name => "Martha",
:amount => 4.0
}
],
[
{
:id => 2,
:accepted => true,
:trans => 7,
:name => "Bob",
:amount => 35.0
},
{
:id => 2,
:accepted => true,
:trans => 7,
:name => "John",
:amount => 5.0
}
]
logic for creating hash
imports = ListImports.limit(20).order(created_at: :DESC)
groups = imports.map{|import| ListImportGroup.where(list_import_id: import.id)}
pub_hash_true = []
pub_hash_false = []
hash = []
imports.map do |import|
hash << {
id: import.id,
trans: import.trans,
accepted: import.amount
}
end
hash.each do |import|
groups.flatten.each do |group|
accepted = import[:accepted]
num_transactions = import[:trans]
if accepted == false
pub_hash_false << {id: import[:id], accepted: accepted, trans: num_transactions, name: group.name, amount: group.amount}
else
pub_hash_true << {id: import[:id], accepted: accepted, trans: num_transactions, name: group.name, amount: group.amount}
end
end
end
# Note: You didn't specify what is the association between `ListImport` and `ListImportGroup`.
# However, I'm fairly sure you could be fetching this data via a JOIN query like below,
# rather than making up to 20 additional database calls to fetch the associated records.
imports = ListImports.limit(20).order(created_at: :DESC).includes(:list_import_group)
result = imports.map do |import|
{
id: import.id,
trans: import.trans,
accepted: import.amount,
names: import.list_import_groups.pluck(:name, :amount)
}
end
And if you do actually need to filter for imports where accepted is true or false, you could do something like this instead of building separate arrays manually:
accepted_imports = result.select { |import| import[:accepted] }
# and
rejected_imports = result.reject { |import| import[:accepted] }
# or even:
accepted_imports, rejected_imports = result.partition { |import| import[:accepted] }
You didn't specify the exact correspondence between the desired and current formats.
But I assume
For the entries with the same id, the values of accepted and trans are identical.
the desired amount for Joe in the current format is identical in the corresponding amount in the desired amount. (In your example, the former is 6.0 whereas the latter is "$1,698.00", which does not make sense.)
Then, the following would do the conversion. The array ahout is in the desired format.
# Let us assume "a1" is the original array in the "current format"
hout = {}
a1.flatten.map{|h|
h.slice(*(%i(id trans name amount accepted))).values
}.each{ |a|
hout[a[0]] = {id: a[0], accepted: a[4], trans: a[1], names: []} if !hout.key? a[0]
hout[a[0]][:names].push(
{name: a[2], amount: "$"+helper.number_with_precision(a[3], precision: 2, delimiter: ',')}
)
}
ahout = hout.values
You may want to sort ahout, if you like.
Note that I am assuming you are using Rails 5+. Otherwise, the method helper may not work. In that case, you can use sprintf or whatever formatting method.

Grabbing a specific hash in an array that meets a specific criteria

I have a huge array full of a bunch of hashes. What I need to do is single out one index hash from the array that meets a specific criteria. (doing this due to an rspec test, but having trouble singling out one of them)
My array is like this
[
{
"name" => "jon doe",
"team" => "team2",
"price" => 2000,
"eligibility_settings" => {}
},
{
"name" => "jonny doe",
"team" => "team1",
"value" => 2000,
"eligibility_settings" => {
"player_gender" => male,
"player_max_age" => 26,
"player_min_age" => 23,
"established_union_only" => true
}
},
{
"name" => "jonni doe",
"team" => "team3",
"price" => 2000,
"eligibility_settings" => {}
},
]
I need to single out the second one, based on its eligibility settings. I just took three of them from my array, have lots more, so simple active record methods like (hash.second) won't work in this instance.
I've tried things like
players.team.map(&:hash).find{ |x| x[ 'eligibility_settings?' ] == true}
However when I try this, I get a nil response. (which is odd)
I've also looked into using the ruby detect method, which hasn't gotten me anywhere either
Players.team.map(&:hash).['hash.seligibiltiy_settings'].detect { true }
Would anybody have any idea what to do with this one?
Notes
players.team.map(&:hash).find{ |x| x[ 'eligibility_settings?' ] == true}
Players.team.map(&:hash).['hash.seligibiltiy_settings'].detect { true }
Is is players or Players ?
Why is it plural?
If you can call map on team, it probably should be plural
Why do you convert to a hash?
eligibility_settings? isn't a key in your hash. eligibility_settings is
eligibility_settings can be a hash, but it cannot be true
If you want to check if it isn't empty, use !h['eligibility_settings'].empty?
Possible solution
You could use :
data = [
{
'name' => 'jon doe',
'team' => 'team2',
'price' => 2000,
'eligibility_settings' => {}
},
{
'name' => 'jonny doe',
'team' => 'team1',
'value' => 2000,
'eligibility_settings' => {
'player_gender' => 'male',
'player_max_age' => 26,
'player_min_age' => 23,
'established_union_only' => true
}
},
{
'name' => 'jonni doe',
'team' => 'team3',
'price' => 2000,
'eligibility_settings' => {}
}
]
p data.find { |h| !h['eligibility_settings'].empty? }
# {"name"=>"jonny doe", "team"=>"team1", "value"=>2000, "eligibility_settings"=>{"player_gender"=>"male", "player_max_age"=>26, "player_min_age"=>23, "established_union_only"=>true}}
If h['eligibility_settings'] can be nil, you can use :
data.find { |h| !h['eligibility_settings'].blank? }
or
data.find { |h| h['eligibility_settings'].present? }

How to display attributes of object when using jbuilder?

I want to show an employee, and all of their reports like the following:
{
name :'ceo'
salary: '1000000'
directs:
{
name: 'sally',
salary: '100000'
},
{
name: 'phil',
salary: '100000'
}
}
I must have a defect such that I only get everything under directs. Here's the jbuilder code for the show action:
json.extract! #employee, :name, :salary
json.array! #employee.direct_reports do |d|
json.name d.name
json.salary d.salary
end
I've tried several iterations of the first part of the code, but I continually see the following on a rest call, for example http://localhost:3000/employees/1.json:
[
{
name: 'sally',
salary: '100000'
},
{
name: 'phil',
salary: '100000'
}
]
Make sure you have respond_to :json inside the respective controller and you have set #employee variable right.
Also try stopping and starting the rails application.
Check out this jbuilder snippet:
json.extract! #employee, :name, :salary
json.directs #employee.direct_reports do |d|
json.name d.name
json.salary d.salary
end
to get this:
{
name :'ceo'
salary: '1000000'
directs:
{
name: 'sally',
salary: '100000'
},
{
name: 'phil',
salary: '100000'
}
}
you need
json.extract! #employee, :name, :salary
json.name :name
json.salary :salary
json.array! #employee.direct_reports do |d|
json.name d.name
json.salary d.salary
end

create a specific hash (dynamic)

How I can create a hash like this in a cycle ?
User.items.each do |m|
......
Result:
test = [{:name => 'Unit 1', :price => "10.00"},
{:name => 'Unit 2', :price => "12.00"},
{:name => 'Unit 3', :price => "14.00"}]]
You can use map to return hashes that you build.
Assuming your Item resource responds to name and price, it would look like
test = User.items.map do |m|
{
name: m.name,
price: m.price
}
end
You also can do like this:
Item.connection.select_all("select name, price from items where user_id = xxxxx;")
you will get an array containing hash, like this:
[{"name"=>"xxx", "price"=> xxx},{}......]

Munged arrays for JSON return in Rails

In a controller method I am doing this:
destination = Destination.new(params[:destination])
trip = Trip.find(params[:trip_id])
destination.city_id = params[:city_id]
destination.trip_id = params[:trip_id]
last_destrination = Destination.find(:last, :order => 'sort', :conditions => { :trip_id => params[:trip_id] })
destination.sort = last_destrination ? last_destrination.sort.to_i + 1 : 1
if trip.user_id == current_user.id and destination.save
city = City.find(params[:city_id])
render :json => [ 'destination' => destination, 'city' => city ]
else
render :status => 500
end
Now I want to send the destination I just make along with the city associated with it. How the heck can I do that, as right now I get the following :
[
{
city: {
city: {
# name: "Fort Frances"
# latitude: 48.617
# created_at: "2010-12-23T16:04:00Z"
# updated_at: "2010-12-23T16:04:00Z"
# country_id: 43
# timezone: "-05:00"
# id: 951
# region_id: 34
# longitude: -93.417
}
},
destination: {
destination: {
# trip_id: 10
# created_at: "2010-12-23T21:24:27Z"
# updated_at: "2010-12-23T21:24:27Z"
# id: 29
# sort: 18
# city_id: 951
}
}
}
]
That all works fine except for the doubling up of array key names. destination.destination is no good. Any ideas?
render :json => [ destination, city ]
--edit
This should render a single object with destination and city keys:
ActiveRecord::Base.include_root_in_json = false
render :json => { :destination => destination, :city => city }
That setting would probably be better in a separate initializer if its used throughout the application.

Resources