I'm trying to flatten foreign key data into a class from a JSON feed. I added the field to the fromJson factory method and it doesn't error out on the browser console(Dartium). When I display it, the field is blank so it looks like it's not getting through, which isn't a surprise. I can't find any documentation on the web for the method. This is my JSON data:
{
"id": 386,
"artist_id": 57,
"label_id": 5,
"style_id": 61,
"title": "A Flower is a Lovesome Thing",
"catalog": "OJCCD-235",
"alternate_catalog": null,
"recording_date": "1957-04-01",
"notes": null,
"penguin": "**(*)",
"category": "jazz",
"label": {
"label_name": "Fantasy"
}
},
This is the method:
factory Record.fromJson(Map<String, dynamic> record) =>
new Record(_toInt(record['id']),
record['title'],
record['catalog'],
record['artist_id'],
record['label_id'],
record['style_id'],
record['alternate_catalog'],
DateTime.parse(record['recording_date']),
record['notes'],
record['penguin'],
record['category'],
record['label_name']
);
This is the invocation:
HttpRequest response = await HttpRequest.request(
url, requestHeaders: headers);
List data = JSON.decode(response.responseText);
final records = data
.map((value) => new Record.fromJson(value))
.toList();
return records;
I've also tried label:label_name in the from Json method. Is it possible to continue to use fromJson to instantiate the object? Is there documentation anywhere that would explain fromJson? I've found some, but it says almost nothing. I'm also looking into flattening it in the Rails serializer or, as a last resort creating a view in the database. As you may notice, I have two other foreigns keys yet to be handled.
Plan B
Günter's answer fixes the problem on the client side. There's also a Rails solution if any one reading would prefer. It requires Active Model Seriializer. Here the pertinent part:
class RecordSerializer < ActiveModel::Serializer
attributes :id, :artist_id, :label_id, :style_id, :title, :catalog, :alternate_catalog,
:recording_date, :notes, :penguin, :category, :label_name
def label_name
object.label.name
end
end
The instruction object.label.name retrieves the name value from the label table. This is the resulting JSON:
{
"id": 386,
"artist_id": 57,
"label_id": 5,
"style_id": 61,
"title": "A Flower is a Lovesome Thing",
"catalog": "OJCCD-235",
"alternate_catalog": null,
"recording_date": "1957-04-01",
"notes": null,
"penguin": "**(*)",
"category": "jazz",
"label_name": "Fantasy"
},
Not entirely sure I understand the question but I guess this is what you're looking for
record['label']['label_name']
Related
I am working on rails 6 with ruby-2.6.5 and i am working on the API. I am using nested attributes for my order as follows:-
orders_controller.rb
# frozen_string_literal: true
module Api
module V1
class OrdersController < Api::V1::ApiApplicationController
before_action :validate_token
def create
debugger
order = OrderInteractor.create(order_params, #user_id)
if order.save
render json: { 'message' => 'Order Placed' }, status: :ok
else
render_errors(order)
end
end
private
def order_params
params.require(:data)
.require(:attributes)
.require(:order)
.permit(:user_id, :address_id, :total_price, :payment_status,
:order_number, :delivery_time_slot,
order_details_attributes:
%i[price quantity order_detail_status product_id
order_number variant_id],
payment_details_attributes:
%i[payment_data payment_id])
end
end
end
end
Api Request:-
{
"data": {
"attributes": {
"order": {
"address_id": "82",
"delivery_time_slot": "5:00 PM - 8:00 PM(Today)",
"order_details_attributes": [{
"price": "76.0",
"product_id": "46",
"quantity": "4",
"variant_id": "47"
}, {
"price": "9.9",
"product_id": "30",
"quantity": "1",
"variant_id": "29"
}],
"payment_details_attributes": [{
"payment_data": {
"data": {
"nameValuePairs": {
"razorpay_payment_id": "pay_HiHceX2p6450Wa",
"org_logo": "",
"org_name": "Razorpay Software Private Ltd",
"checkout_logo": "https://cdn.razorpay.com/logo.png",
"custom_branding": false
}
},
"paymentId": "pay_HiHceX2p6450Wa",
"userContact": "+916494949494",
"userEmail": "dailyferia#gmail.com"
}
}],
"total_price": "354"
}
},
"type": "orders"
}
}
While placing order i am getting the error Unpermitted parameter: :payment_data but it's working fine for the order_details. Please help me to fix it? I also tried the below ways to fix it but nothing worked:-
payment_details_attributes: %i[:payment_data payment_id]) and `payment_details_attributes: ['payment_data', 'payment_id'])`
Your payment_data is a complex object, rather than the scalars that are found in your order_details_attributes
You will need to add more to the permitted parameters, I believe the simplest solution would be:
payment_details_attributes: [payment_data: {}]
This should accept all parameters under payment_details_attributes, but it would also permit any other keys as well. You may want to be more strict and only allow the parameters specified above, in which case you could do:
payment_details_attributes: [
payment_data: {
data: {
nameValuePairs:
%i[razorpay_payment_id org_logo org_name checkout_logo custom_branding]
},
:paymentId, :userContact, :userEmail
}
]
which should restrict the parameters to just the format used in your example.
A few other notes:
You have %i[payment_data payment_id] in your original sample, but there is no payment_id in your payload. The attribute in the sample is paymentId, and on top of that, it is an attribute of the payment_data, not the payment_details_attributes
you wouldn't use %i and a colon, the %i is a shorthand for creating an array of ruby symbols, so %i[:payment_data payment_id] would create the array [:":payment_data", :payment_id] (note the extra colon at the beginning of payment_data)
Lastly, I haven't tested my code above, so there could be a syntax or other error, but hopefully this points you in the right direction.
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
},
}
I am using an API call which returns a JSON response. I want to access the data inside the response so I can create some nice display cards showing the info and pictures. Here is a snippet from the response, the response property is populated with about 20 objects I'll include just two for brevity:
{
"success": true,
"message": "",
"result": [
{
"MarketCurrency": "LTC",
"BaseCurrency": "BTC",
"MarketCurrencyLong": "Litecoin",
"BaseCurrencyLong": "Bitcoin",
"MinTradeSize": 1e-8,
"MarketName": "BTC-LTC",
"IsActive": true,
"Created": "2014-02-13T00:00:00",
"Notice": null,
"IsSponsored": null,
"LogoUrl": "https://i.imgur.com/R29q3dD.png"
},
{
"MarketCurrency": "DOGE",
"BaseCurrency": "BTC",
"MarketCurrencyLong": "Dogecoin",
"BaseCurrencyLong": "Bitcoin",
"MinTradeSize": 1e-8,
"MarketName": "BTC-DOGE",
"IsActive": true,
"Created": "2014-02-13T00:00:00",
"Notice": null,
"IsSponsored": null,
"LogoUrl": "https://i.imgur.com/e1RS4Hn.png"
},
In my Rails controller I'm using JSON.parse and I'm trying to turn it into an object with the Open struct option:
#markets = JSON.parse(markets.to_json, object_class: OpenStruct)
In my view I'll do this <%=#markets.class%> and it shows Array and not object. So I try this <%=#markets.size%> and it shows 1. If I do <%=#markets[0]['success']%> I would expect it to return true but it returns 'success'. So, I'm not understanding why the ostruct library isn't working like I would expect or how I can get to the objects stored in the result array. Any help is greatly appreciated!
You already have a JSON response, isn't needed to use to_json again, try just parsing that object, and then use the dot . to access its fields, as an OpenStruct object now then you can access them as methods:
require 'json'
a = '{
"success": true,
"message": "",
"result": [{
"MarketCurrency": "LTC",
"BaseCurrency": "BTC",
"MarketCurrencyLong": "Litecoin",
"BaseCurrencyLong": "Bitcoin",
"MinTradeSize": 1e-8,
"MarketName": "BTC-LTC",
"IsActive": true,
"Created": "2014-02-13T00:00:00",
"Notice": null,
"IsSponsored": null,
"LogoUrl": "https://i.imgur.com/R29q3dD.png"
}, {
"MarketCurrency": "DOGE",
"BaseCurrency": "BTC",
"MarketCurrencyLong": "Dogecoin",
"BaseCurrencyLong": "Bitcoin",
"MinTradeSize": 1e-8,
"MarketName": "BTC-DOGE",
"IsActive": true,
"Created": "2014-02-13T00:00:00",
"Notice": null,
"IsSponsored": null,
"LogoUrl": "https://i.imgur.com/e1RS4Hn.png"
}]
}'
b = JSON.parse(a, object_class: OpenStruct)
p b.success
# => true
After much debugging and some assistance, I was able to get it to work. The response from the API call was an array with one item. The item was a long string of the entire dataset.
In order to get the expected behavior of "true" when calling #markets.success, I first had to
raw_markets = JSON.parse(markets.to_json)
followed by
#markets = raw_markets.map do |market|
JSON.parse(market, object_class: OpenStruct)
Note: the variable markets holds the original api call:
markets = open('url-to-api')
After this I would get #markets.success = "true" and #markets.result[0] held the first result, #markets.result[1] held the second result, and so on.
What I’m trying to do is loop through a hash and save certain key’s values to the database. This hash has nested keys, and I’m struggling to find a suitable way to loop through it.
First, I’m parsing the JSON of photos (from 500px API), and putting the response into terminal:
def index
#photos = JSON.parse(get_access_token.get('/v1/photos/search?term=california').body)
p #photos
save #photos
end
The response I get in console is all okay and looks like this. (I’ve cut it down so it doesn’t take up too much room):
{
"current_page": 1,
"total_pages": 50,
"total_items": 8263,
"photos": [
{
"id": 4930535,
"name": "Bike",
"description": "",
"times_viewed": 28,
"rating": 27,
"created_at": "2012-02-10T00:39:03-05:00"
},
{
"id": 4930206,
"name": "Rain",
"description": "",
"times_viewed": 1,
"rating": 59.7,
"created_at": "2012-02-10T00:04:09-05:00"
},
{
"id": 4930202,
"name": "California",
"description": "",
"times_viewed": 100,
"rating": 58.2,
"created_at": "2012-02-10T00:05:25-05:00"
}
]
}
I’m then trying to loop through the photos and save the name, description and times_viewed to the db, using this save method.
def save photos
photos.each do |photo|
p = Photo.new(:name => photo["photos"]["name"], :description => photo["photos"]["description"], :times_viewed => photo["photos"]["times_viewed"])
p.save
end
end
The trouble is that the photos key is nested, and it throws this error in terminal:
TypeError (no implicit conversion of String into Integer):
app/controllers/photos_controller.rb:18:in `[]'
app/controllers/photos_controller.rb:18:in `block in save'
app/controllers/photos_controller.rb:17:in `each'
app/controllers/photos_controller.rb:17:in `save'
app/controllers/photos_controller.rb:10:in `index'
Just take the photos array out of your json response and iterate over that. This way you only have one layer of hash keys to reference:
json_response['photos'].each do |photo|
Photo.create name: photo['name'], description: photo['description'],
times_viewed: photo['times_viewed']
end
photos["photos"] is an array so you need to specify the index before name. The implementation of your save method isn't completely clear, but I believe your parameters for the new method should have the form:
:name => photo["photos"][index]["name"]
You're getting a conversion error since the compiler is trying to convert name into an index.
So I'm using the Shopify Gem to access the Shopify API and noticed that the product_id attribute is not being returned within the response body for a simple ShopifyAPI::Variant.find call.
1.9.3p194> ShopifyAPI::Variant.find(209901733)
=> #<ShopifyAPI::Variant:0x007fbf7225d3f0 #attributes={"barcode"=>nil, "compare_at_price"=>"198.00", "created_at"=>"2012-03-23T14:11:39+11:00", "fulfillment_service"=>"manual", "grams"=>1000, "id"=>209901733, "inventory_management"=>"shopify", "inventory_policy"=>"deny", "option1"=>"38", "option2"=>"Ivory Mini Twill", "option3"=>nil, "position"=>16, "price"=>"198.00", "requires_shipping"=>true, "sku"=>"3063", "taxable"=>true, "title"=>"38 / Ivory Mini Twill", "updated_at"=>"2013-04-24T10:25:27+10:00", "inventory_quantity"=>2}, #prefix_options={}, #persisted=true>
According to the new documentation that has been published here, the product_id field should be returned.
GET /admin/variants/#{id}.json
Hide Response
HTTP/1.1 200 OK
{
"variant": {
"barcode": "1234_pink",
"compare_at_price": null,
"created_at": "2013-05-01T15:35:21-04:00",
"fulfillment_service": "manual",
"grams": 200,
"id": 808950810,
"inventory_management": "shopify",
"inventory_policy": "continue",
"option1": "Pink",
"option2": null,
"option3": null,
"position": 1,
"price": "199.00",
"product_id": 632910392,
"requires_shipping": true,
"sku": "IPOD2008PINK",
"taxable": true,
"title": "Pink",
"updated_at": "2013-05-01T15:35:21-04:00",
"inventory_quantity": 10
}
}
It is in the json, but not in the ActiveResource that is created from the json. The reason is this code in the Variant activeresource:
self.prefix = "/admin/products/:product_id/"
def self.prefix(options={})
options[:product_id].nil? ? "/admin/" : "/admin/products/#{options[:product_id]}/"
end
If you want you can make your own class for fetching singleton Variants:
module ShopifyAPI
class VariantWithProduct < Base
self.prefix = "/admin/"
self.element_name = "variant"
self.collection_name = "variants"
end
end
and use this class to fetch single variants by id:
ShopifyAPI::VariantWithProduct.find(xxxxxx)
Michael is correct in his diagnosis of the problem. For me, the easiest way around this was to get the product resource instead of the variant. The ShopifyAPI::Product ActiveResource object does include variants.
product = ShopifyAPI::Product.find(product_id)
variant = product.variants.find { |v| v.id == variant_id }