serializing array when nested with other attributes in rails 5 - ruby-on-rails

I have a ruby (2.4.0p0) rails (5.0.2) controller from which I wish to return a json result containing a list of Thing objects as well as some high level info (such as next and previous from Kaminari paging).
Consider a Thing with an association to Owner. Thing has a owner_id attribute.
For #things = Thing.page(1).per(2) I will be able to use
render json: #things
and get;
[
{ "id": 1, "owner_id": 1, "name": "thing1" },
{ "id": 2, "owner_id": 1, "name": "thing2" }
]
Good. If I then create a serializer called ThingSerializer.rb and define owner such that it adds "owner":"CatInHat" instead of "owner_id":1
This works as well;
[
{ "id": 1, "owner": "CatInHat", "name": "thing1" },
{ "id": 2, "owner": "CatInHat", "name": "thing2" }
]
This is good, but, my problem comes when I want to add higher level data and label the list as "results" such as when I try;
render json: { next:"some_url_link",previous:"some_other_url_link", results: #bags}
I'd like to get;
{ "next":some_url_link,
"prev":some_other_url_link,
"results":[ { "id": 1, "owner": "CatInHat", "name": "thing1" }, { "id": 2, "owner": "CatInHat", "name": "thing2" } ]
}
What I get is nearly the above but with "owner_id":1 instead of "owner":"CatInHat" - my serializer does not seem to be used when I label and nest my list of things. What is the appropriate way to use my serializer and get this output?
If I create config/initializers/active_model_serializers.rb and add
ActiveModel::Serializer.config.adapter = :json_api
It gives me an api which is similar but I don't know if it can be customized to fit the spec I need above.
thank you for any help

It looks like the serialization logic in render json: ... only kicks in if the attribute is an ActiveRecord object or an array of ActiveRecord objects. Since you are giving it a hash, it will not inspect the individual attributes and recursively apply the serializers.
You can try manually applying ThingSerializer:
render json: {
next: ...,
prev: ...,
results: #things.map { |thing|
ThingSerializer.new(thing).attributes
},
}

Related

Get some data from a table in my database - Ruby on rails

I have an association of my model Banner with Images, I need to bring all the images but only the field file, in file I have a hash in which I just want to get the url
I have this code:
Banner.find_by(event_id: #event.id).to_json(:include => [{:images => {:only => [:file]}}])
but this gets me this:
{
"id": 2,
"created_at": "2019-04-24T14:59:08.000-05:00",
"updated_at": "2019-04-24T14:59:08.000-05:00",
"event_id": 3,
"name": "ccccccccccccccccccccccssssss",
"images": [
{
"file": {
"url": "/uploads/image/file/300aecf6-b3c7-4b15-94a1-45c530efc4c4.png"
}
}
]
}
I want something like this:
{
"id": 2,
"created_at": "2019-04-24T14:59:08.000-05:00",
"updated_at": "2019-04-24T14:59:08.000-05:00",
"event_id": 3,
"name": "ccccccccccccccccccccccssssss",
"images": [
"/uploads/image/file/300aecf6-b3c7-4b15-94a1-45c530efc4c4.png"
]
}
How could I do this? Any suggestions?
Sometimes it's best to break things up into smaller pieces...
# create the query to get all the objects to prevent n+1 queries
banners = Banner.includes(images: :file)
# get the banner you want to serialize
banner = banners.find_by(event_id: #event.id)
# get all the urls for the images as an array
image_urls = banner.images.collect {|i| i.file.url }
# create the json object
json = banner.to_json.merge(images: image_urls)

Rails 4 - Iterate through nested JSON params

I'm passing nested JSON into rails like so:
{
"product": {
"vendor": "Acme",
"categories":
{
"id": "3",
"method": "remove",
},
"categories":
{
"id": "4"
}
}
}
in order to update the category on a product. I am trying to iterate through the categories attribute in my products_controller so that I can add/remove the product to multiple categories at once:
def updateCategory
#product = Product.find(params[:id])
params[:product][:categories].each do |u|
#category = Category.find_by(id: params[:product][:categories][:id])
if params[:product][:categories][:method] == "remove"
#product.remove_from_category(#category)
else
#product.add_to_category(#category)
end
end
end
However, this only uses the second 'categories' ID in the update and doesn't iterate through both.
Example response JSON:
{
"product": {
"id": 20,
"title": "Heavy Duty Aluminum Chair",
"product_price": "47.47",
"vendor": "Acme",
"categories": [
{
"id": 4,
"title": "Category 4"
}
]
}
}
As you can see, it only added the category with ID = 4, and skipped over Category 3.
I'm fairly new to rails so I know I'm probably missing something obvious here. I've played around with the format of the JSON I'm passing in as well but it only made things worse.
You need to change your JSON structure. As you currently have it, the second "categories" reference will override the first one since you can only have 1 instance of a key. To get what you want, you should change it to:
{
"product": {
"vendor": "Acme",
"categories": [
{
"id": "3",
"method": "remove",
},
{
"id": "4"
}
]
}
}
You will also need to change your ruby code to look like:
def updateCategory
#product = Product.find(params[:id])
params[:product][:categories].each do |u|
#category = Category.find_by(id: u[:id])
if u[:method] == "remove"
#product.remove_from_category(#category)
else
#product.add_to_category(#category)
end
end
end

Ruby/Rails - interate over complex (nested) JSON elements to create objects

I'm parsing some JSON from a mixed content source, and with it trying to store it with ActiveRecord.
At the moment I'm using a ton of variables:
json['settings']['newsletters']['weekly']
json['info']['address']['city']
Or trying to make things a little easier:
newsletters = json['settings']['newsletters']
newsletters['weekly']
address = json['info']['address']
address['city']
But this is all getting very messy, and not DRY.
I think the better way to do this would be to iterate over each element that is a hash (and therefore 'complex'), and assign it it's own object. This way, I don't have to declare a trillion variables, they can instead be assigned from the context of the JSON input.
So, I can do something like this:
user = json['input']
user.settings.newsletters.weekly
user.info.address.city
This is inspired by what ActiveResource documents:
# Any complex element (one that contains other elements) becomes its own object:
#
# {"id":1,"first":"Tyler","address":{"street":"Paper St.","state":"CA"}}
tyler = Person.find(1)
tyler.address # => <Person::Address::xxxxx>
tyler.address.street # => 'Paper St.'
Here is the JSON, reduced for brevity's sake:
{
"username": "robert_fitzsimmonds",
"emails": [{
"id_number": 1,
"address": "robert_fitzsimmonds#yahoo.com",
"confirmed": false
}, {
"id_number": 2,
"address": "robert_fitzsimmonds#gmail.com",
"confirmed": true
}],
"settings": {
"marketing": {
"main": true,
"weekly": false,
"daily": false
},
"language": "English"
},
"info": {
"address": {
"line_1": "31 Mole Road",
"line_2": "",
"city": "London",
"post_code": "NE4 5RJ"
},
"shared_account": false
}
}
Would such an iteration be the most efficient solution, or is it best to stick to long, messy variables?
Use the hash_dot gem if you can https://github.com/adsteel/hash_dot

Extract specific field from JSON nested hashes

I am thinking of writing a web application that crawls an API and returns this information in JSON form.
However, I am only after one number, then current price (in this sample, "227"). How can I access that in Ruby? I have no clue where to begin. I've never dealt with text like this.
For discussion's sake, suppose I save this output into instance variable #information
{
"item": {
"icon": "http://services.runescape.com/m=itemdb_rs/4332_obj_sprite.gif?id=4798",
"icon_large": "http://services.runescape.com/m=itemdb_rs/4332_obj_big.gif?id=4798",
"id": 4798,
"type": "Ammo",
"typeIcon": "http://www.runescape.com/img/categories/Ammo",
"name": "Adamant brutal",
"description": "Blunt adamantite arrow...ouch",
"current": {
"trend": "neutral",
"price": 227
},
"today": {
"trend": "neutral",
"price": 0
},
"day30": {
"trend": "positive",
"change": "+1.0%"
},
"day90": {
"trend": "positive",
"change": "+1.0%"
},
"day180": {
"trend": "positive",
"change": "+2.0%"
},
"members": "true"
}
}
First follow this post to parse this JSON in to Hash
Parsing a JSON string in Ruby
say the parsed hash name is my_hash then the following should give you price
my_hash['item']['current']['price']
Edit:
As you said you want to save it in #information
#information = my_hash['item']['current']['price']
Even you can use hashie it gives your json into readable structural code
Install Hashie
gem install hashie
then in your code all that json take in a variable my_json
myhash = Hashie::Mash.new(my_json)
#information = my_hash.item.current.price
Tips:-
if your json is dynamic and it may respond some other structural element so you can maintain exceptional code
#information = my_hash.item.try(:current).try(:price)

How to have two pieces of data output as json in rabl

New to rabl and not sure how to do this with two different arrays returned in a single hash like this:
#data={:locations => [location1, location2], :items => [item1,item2]}
In my rabl file, I'd like to do something like the following:
#data[:locations]
extends "api/location_show"
#data[:items]
extends "api/item_show"
to output this:
{
"locations": [
{
"id": 156,
"name": "Location 1"
},
{
"id": 158,
"name": "Location 2"
}
],
"items": [
{
"global_id": 3189,
"header": "pistachio 1"
},
{
"global_id": 3189,
"header": "pistachio 2"
}
]
}
but it just doesn't seem to be working. Is there a way to get this to work?
thx
Your rabl file should look something like:
object false
child (:locations) { attributes :id, :name }
child (:items) { attributes :global_id, :header }
By setting object to false, you essentially tell rabl that you want to construct your nodes on your own. Then you can go ahead and invoke the child and node methods as you wish.

Resources