Removing links using JasonApi Resources Gem - ruby-on-rails

I have a simple model with the following classes:
class Country < ApplicationRecord
has_many :country_categories
has_many :categories, through: :country_categories
end
class CountryCategory < ApplicationRecord
belongs_to :country
belongs_to :category
end
class Category < ApplicationRecord
has_many :country_categories
has_many :countries, through: :country_categories
end
and the resources:
class CountryResource < JSONAPI::Resource
has_many :country_categories
end
When I do a GETrequest to /countries/1?include=country_categories.category the response always brings a lot of links. For example
{
"data": {
"id": "31",
"type": "countries",
"attributes": {
"description": null
},
"relationships": {
"country-categories": {
"links": {
"self": "http://api.localhost.local:3000/api/v1/countries/31/relationships/country-categories",
"related": "http://api.localhost.local:3000/api/v1/countries/31/country-categories"
},
"data": [
{
"type": "country-categories",
"id": "129"
},
{
"type": "country-categories",
"id": "130"
}
]
},
}
},
"included": [
{
"id": "129",
"type": "country-categories",
"attributes": {
"stuff": "aaa"
},
"relationships": {
"country-subcategories": {
"links": {
"self": "http://api.localhost.local:3000/api/v1/country-categories/129/relationships/country-subcategories",
"related": "http://api.localhost.local:3000/api/v1/country-categories/129/country-subcategories"
}
},
"category": {
"links": {
"self": "http://api.localhost.local:3000/api/v1/country-categories/129/relationships/category",
"related": "http://api.localhost.local:3000/api/v1/country-categories/129/category"
},
"data": {
"type": "categories",
"id": "1"
}
}
}
},
{
"id": "130",
"type": "country-categories",
"attributes": {
"stuff": "sfasf"
},
"relationships": {
"country-subcategories": {
"links": {
"self": "http://api.localhost.local:3000/api/v1/country-categories/130/relationships/country-subcategories",
"related": "http://api.localhost.local:3000/api/v1/country-categories/130/country-subcategories"
}
},
"category": {
"links": {
"self": "http://api.localhost.local:3000/api/v1/country-categories/130/relationships/category",
"related": "http://api.localhost.local:3000/api/v1/country-categories/130/category"
},
"data": {
"type": "categories",
"id": "3"
}
}
}
},
{
"id": "1",
"type": "categories",
"links": {
"self": "http://api.localhost.local:3000/api/v1/categories/1"
},
"attributes": {
"name": "Value extraction"
},
"relationships": {
"subcategories": {
"links": {
"self": "http://api.localhost.local:3000/api/v1/categories/1/relationships/subcategories",
"related": "http://api.localhost.local:3000/api/v1/categories/1/subcategories"
}
}
}
},
{
"id": "3",
"type": "categories",
"links": {
"self": "http://api.localhost.local:3000/api/v1/categories/3"
},
"attributes": {
"name": "Enabling environment"
},
"relationships": {
"subcategories": {
"links": {
"self": "http://api.localhost.local:3000/api/v1/categories/3/relationships/subcategories",
"related": "http://api.localhost.local:3000/api/v1/categories/3/subcategories"
}
}
}
}
]
}
All these links aren't going to be used. Is there a way I can remove them?
Thank you :)

The solution I found so far was to monkey patch the Resource Serializer (even though this solution is far from perfect).
module JSONAPI
class ResourceSerializer
def link_object_to_many(source, relationship, include_linkage)
include_linkage = include_linkage | relationship.always_include_linkage_data
link_object_hash = {}
link_object_hash[:links] = {} if relationship.always_include_linkage_data
link_object_hash[:links][:self] = self_link(source, relationship) if relationship.always_include_linkage_data
link_object_hash[:links][:related] = related_link(source, relationship) if relationship.always_include_linkage_data
link_object_hash[:data] = to_many_linkage(source, relationship) if include_linkage
link_object_hash
end
def link_object_to_one(source, relationship, include_linkage)
include_linkage = include_linkage | #always_include_to_one_linkage_data | relationship.always_include_linkage_data
link_object_hash = {}
link_object_hash[:links] = {} if relationship.always_include_linkage_data
link_object_hash[:links][:self] = self_link(source, relationship) if relationship.always_include_linkage_data
link_object_hash[:links][:related] = related_link(source, relationship) if relationship.always_include_linkage_data
link_object_hash[:data] = to_one_linkage(source, relationship) if include_linkage
link_object_hash
end
end
end

Related

Is there a way to select just one attribute of an outer object by using the Fast JSON API in Rails?

I am creating a travel app which uses a backend Rails API. I've decided to use the Fast JSON API to serialize my data. I have a list of countries; each country has many cities, and each city has many attractions.
These are a list of my associations between my models.
Country.rb
has_many :cities
has_many :attractions, through: :locality
Localities.rb
has_many :attractions
belongs_to :country
Attraction.rb
belongs_to :locality
When I serialize my data for an individual attraction, I would like to include only the name attribute of the city and the name attribute of the country it belongs to. I am currently doing this by adding the optional parameter to include the locality and country name.
def show
attraction = Attraction.find_by(slug: params[:slug])
options = {}
options[:include] = [:locality, :'locality.country.name']
render json: AttractionSerializer.new(attraction, options).serialized_json
end
However, this gives all the attributes and relationships of the country, including a list of all unrelated localities nested within the country, which will become really inefficient when my dataset becomes larger. See below:
{
"data": {
"id": "1",
"type": "attraction",
"attributes": {
"name": "Plaza de EspaƱa",
"description": "Site of the World Exposition in 1929",
"types": null,
"latitude": 40.4232824,
"longitude": -3.7107257,
"slug": "plaza-de-espana",
"locality": {
"id": 1,
"name": "Seville",
"country_id": 168,
"created_at": "2020-06-10T05:43:47.474Z",
"updated_at": "2020-06-10T05:43:47.474Z",
"slug": "seville",
"latitude": 37.3886303,
"longitude": -5.9953403
}
},
"relationships": {
"locality": {
"data": {
"id": "1",
"type": "locality"
}
}
}
},
"included": [
{
"id": "1",
"type": "locality",
"attributes": {
"name": "Seville",
"latitude": 37.3886303,
"longitude": -5.9953403,
"slug": "seville"
},
"relationships": {
"country": {
"data": {
"id": "168",
"type": "country"
}
}
}
},
{
"id": "168",
"type": "country",
"attributes": {
"name": "Spain",
"slug": "spain",
"iso_3166_1_alpha2": "ES",
"iso_3166_1_alpha3": "ESP",
"iso_3166_1_numeric": "724"
},
"relationships": {
"localities": {
"data": [
{
"id": "1",
"type": "locality"
},
{
"id": "2",
"type": "locality"
},
{
"id": "3",
"type": "locality"
},
{
"id": "4",
"type": "locality"
},
{
"id": "5",
"type": "locality"
},
{
"id": "6",
"type": "locality"
}
]
},
"attractions": {
"data": [
{
"id": "1",
"type": "attraction"
}
]
}
}
}
]
}
Is there a way to only include just one attribute (i.e. name of Country) in the Attraction JSON, instead of the whole object? (Attraction is nested two levels below Country)
Thank you very much.
You need to create multiple serializers that work together to achieve this.
class AttractionSerializer
include FastJsonapi::ObjectSerializer
attributes :attraction_attr_1, :attraction_attr_2, etc.
belongs_to :locality, serializer: AttractionLocalitySerializer
end
class AttractionLocalitySerializer
include FastJsonapi::ObjectSerializer
attributes :name
belongs_to :country, serializer: AttractionCountrySerializer
end
class AttractionCountrySerializer
include FastJsonapi::ObjectSerializer
attributes :name
end

Combine ActiveModel serializer results

I have the following ActiveModel serializer which parses the following data:
class CarSerializer < ActiveModel::Serializer
attributes :brand, :wheels, :events
def brand
{ id: object.id, brand_name: object.brand_name }
end
def wheels
object.wheel_details
end
def events
object.car_events
end
end
and the following is the data shows up:
{
"car_info": [
{
"car": {
"id": 1,
"brand_name": "opel"
},
"wheels": [
{
"id": 2,
"size": "23.4"
},
{
"id": 1,
"size": "22.3"
}
],
"events": [
{
"id": 1,
"event_place": "america"
},
{
"id": 1,
"event_place": "asia"
}
]
However, i want the data to look like the following:
{
"car_info": [
{
"car": {
"id": 1,
"brand_name": "opel"
},
"wheels_and_events": [
{
"id": 2,
"size": "23.4"
"event_place": "america"
},
{
"id": 1,
"size": "22.3"
"event_place": "asia"
}
]
I'm using has_many through associations for 3 tables and one table for the relation.

Defining Array of Objects in Swagger Documentation

I'm using swagger for quite a bit now, we have started documenting our code using it, in one place there's an API response which returns multiple objects in the included block.
Example:
{
"data": {
"id": "1",
"type": "schoolPositions",
"attributes": {
"description": "teases the students",
"mustHaves": "principle"
},
"relationships": {
"schoolLocation": {
"data": {
"id": "72",
"type": "schoolLocations"
}
},
"schoolCompensation": {
"data": {
"id": "75",
"type": "schoolCompensations"
}
},
"jobSpecs": {
"data": [
{
"id": "82",
"type": "schoolAttachments"
}
]
}
}
},
"included": [
{
"id": "72",
"type": "schoolLocations",
"attributes": {
"city": "Berhampore",
"state": "West Bengal",
"postalCode": "742101",
"country": "India",
"globalRegionId": 30,
"regionId": 683
}
},
{
"id": "75",
"type": "schoolCompensations",
"attributes": {
"salary": "",
"bonus": "",
"equity": "",
"currencyId": null,
"equityType": "percent",
"salaryDescription": null
}
},
{
"id": "82",
"type": "schoolAttachments",
"attributes": {
"attachmentType": "JobSpecificationAttachmentType",
"fileFileName": "vs.jpg",
"fileContentType": "image/jpeg",
"fileFileSize": 2410039,
"fileUpdatedAt": "2018-12-12T07:06:38Z",
"downloadUrl": "001-vs.jpg?1544598398",
"klass": "SchoolAttachments"
}
}
]
I have wasted an entire day on the internet and documentation trying to document the included part, but I'm going wrong somewhere
response 200 do
key :description, 'School Data'
schema do
property :data do
key :type, :array
items do
key :'$ref', :School
end
end
property :included do
key :type, :array
items do
key :'$ref', :SchoolLocations
key :'$ref', :SchoolCompensations
key :'$ref', :SchoolAttachments
end
end
end
end
This shows only the SchoolAttachments in the included part.
I have tried using allOff but it doesn't work.

JSON API Resources relationship attributes

I have two models related :
class Item < ActiveRecord::Base
belongs_to :carousel
end
And
class Carousel < ActiveRecord::Base
has_many :items
end
I trying to open API to carousels using the JSON API Resources gem, i need to show carousel attributes and some items attributes, something like this:
{
"data": [
{
"id": "1",
"type": "carousels",
"links": {
"self": "http://localhost:3000/api/v1/carousels/1"
},
"attributes": {
"name": "primary",
"items": [
{
"title": "first item",
"file-url": "url",
"index": 0
},
{
"title": "second item",
"file-url": "url",
"index": 1
}
]
}
}
]
}
My carousel resource:
module Api
module V1
class CarouselResource < JSONAPI::Resource
immutable
attributes :name, :items
def self.records(options = {})
user = options[:context][:current_user]
user.carousels
end
end
end
end
My result:
{
"data": [
{
"id": "1",
"type": "carousels",
"links": {
"self": "http://localhost:3000/api/v1/carousels/1"
},
"attributes": {
"name": "primary",
"items": [
{
"id": 3,
"carousel_id": 1,
"file": {
"url": "url"
},
"title": "first item",
"kind": 0,
"index": 0,
"created_at": "2017-02-23T10:31:53.592-03:00",
"updated_at": "2017-03-01T10:30:52.533-03:00"
},
{
"id": 5,
"carousel_id": 1,
"file": {
"url": "url"
},
"title": "second item",
"kind": 0,
"index": 1,
"created_at": "2017-03-01T10:30:07.011-03:00",
"updated_at": "2017-03-01T10:30:07.011-03:00"
}
]
}
}
]
}

Can't map JSON data to model

Currently trying to call an API and map an attribute to my model. e.g. image_src string from the JSON response to image string in my model. But right now It's getting the error 'no implicit conversion of String into Integer'.
Feed.rb
require 'httparty'
require 'json'
class Feed < ActiveRecord::Base
include HTTParty
base_uri 'https://extraction.import.io/query/runtime'
has_many :entries
# GET /feeds
# GET /feeds.json
def fetch_data
response = self.class.get("/2365205f-8502-439e-a6d2-73988cfa03f1?&url=http%3A%2F%2F%2F")
#elements = response.parsed_response["extractorData"]
#elements.map do |image_info|
self.entries.create(image: image_info['url'])
end
end
end
Entry.rb
class Entry < ActiveRecord::Base
belongs_to :feed
end
HTML
<% #feed.entries.each do |image| %>
<div class="grid-item">
<%= image_tag(image) %>
</div>
<% end %>
JSON Response
{
"extractorData": {
"url": "http://linxspiration.com/",
"resourceId": "e26012fd5f25602c1c4e0945a7507e1f",
"data": [
{
"group": [
{
"image": [
{
"src": "http://40.media.tumblr.com/0a38dd25a41e0702940c084b60bee860/tumblr_o5c0tyGhOP1qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142509606341"
}
]
},
{
"image": [
{
"src": "http://36.media.tumblr.com/276def9e46bdfb9efee7f7d4e4444195/tumblr_o5c0szx4F21qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142506402604"
}
]
},
{
"image": [
{
"src": "http://40.media.tumblr.com/4953cdecc24389d94844dfb88c819d8c/tumblr_o055uh8b7h1uhpqwfo1_1280.jpg",
"href": "http://linxspiration.com/post/142503176501/linxsupply-discipline-gets-shit-done-buy-this"
}
]
},
{
"image": [
{
"src": "http://41.media.tumblr.com/353f10283fc3a0237262629b6a395c90/tumblr_o5aadrw6l31qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142499072059"
}
]
},
{
"image": [
{
"src": "http://40.media.tumblr.com/889c65a662a1b690f299593e3581b947/tumblr_o57uysuSjF1tq9q5vo1_1280.jpg",
"href": "http://linxspiration.com/post/142493659142/blazepress-sunrise-in-venice"
}
]
},
{
"image": [
{
"src": "http://45.media.tumblr.com/14c24e549a6559b48933f05ff40e3627/tumblr_o57vmsJ7gk1tq9q5vo1_400.gif",
"href": "http://linxspiration.com/post/142488060049/blazepress-i-lick-paw"
}
]
},
{
"image": [
{
"src": "http://36.media.tumblr.com/f184f397d14563c9e41136c5fe370016/tumblr_o59oo0pUy61qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142476686818"
}
]
},
{
"image": [
{
"src": "http://40.media.tumblr.com/453b70fd4055952e907766a5942cc560/tumblr_o59ohsGHBo1qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142470776914"
}
]
},
{
"image": [
{
"src": "http://41.media.tumblr.com/1de6c873de55ddb899f83441454ff5bb/tumblr_o59ohhnd0k1qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142465333421"
}
]
},
{
"image": [
{
"src": "http://40.media.tumblr.com/f71b3ee53f51a9679dc65096933f2b08/tumblr_o59of8kouq1qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142456009994"
}
]
},
{
"image": [
{
"src": "http://40.media.tumblr.com/b6aa0dc78619a6b9e09b232224c0bfb7/tumblr_o59oeu18Ly1qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142452801623"
}
]
},
{
"image": [
{
"src": "http://41.media.tumblr.com/d1c5a23af31880d10fd89fc8a6a0b8e6/tumblr_o585z3mPuF1tq9q5vo1_1280.jpg",
"href": "http://linxspiration.com/post/142449893982/blazepress-life"
}
]
},
{
"image": [
{
"src": "http://41.media.tumblr.com/03369de74399e12e1901b3751917c512/tumblr_o54gbfJlXx1qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142445969058"
}
]
},
{
"image": [
{
"src": "http://40.media.tumblr.com/6543cbb31ea206a59cbdd1e865d63562/tumblr_o54mncUOEP1qkegsbo1_1280.jpg",
"href": "http://linxspiration.com/post/142440337822"
}
]
}
]
}
]
},
"pageData": {
"statusCode": 200,
"timestamp": 1460206655245
}
}
Any help would be brilliant!
response = self.class.get("/2365205f-8502-439e-a6d2-73988cfa03f1?&url=http%3A%2F%2F%2F")
puts response.parsed_response
Shows that the auth is failing:
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>openresty/1.9.7.3</center>
</body>
</html>
def fetch_data
...
#elements = response.parsed_response["extractorData"]
# To access the image src's:
image_srcs = #elements['data'].first['group'].map{ | z | z['image'].first['src']}
image_srcs.each do |src|
self.entries.create(image: src)
end
end

Resources