I'm trying to write an API endpoint for creating Redemptions in my app.
In Rails, my model is such that Redemption has many Items (class_name: RedemptionItems).
Here's my POST Body, following what I assume is the correct JSONAPI Specification (since there is no explicit spec for creating parent and child records in one request).
{
"data": {
"type": "redemptions",
"relationships": {
"items": {
"data": [{
"type": "items",
"attributes": { "offer_id": "1", "quantity": "2" }
},
{
"type": "items",
"attributes": { "offer_id": "1", "quantity": "3" }
},
{
"type": "items",
"attributes": { "offer_id": "123", "quantity": "3" }
}
]
}
}
}
}
I'm using JSONAPI::Resources. I have defined my JSONAPI::Resources thus:
class Platform::Api::Members::RedemptionItemResource < JSONAPI::Resource
model_name 'Platform::RedemptionItem'
has_one :redemption
end
class Platform::Api::Members::RedemptionResource < JSONAPI::Resource
model_name 'Platform::Redemption'
has_many :items, class_name: 'RedemptionItem'
end
It's currently giving me an inavlid links object error, which doesnt tell me anything on how I must improve my request body.
{
"errors": [
{
"title": "Invalid Links Object",
"detail": "Data is not a valid Links Object.",
"code": "115",
"status": "400"
}
]
}
I'm seeing a strange behaviour regarding rails 5, active model serializer and the json-api adapter.
Given the following User model with the Rolify gem:
class User < ActiveRecord::Base
#
# Gem Includes
#
rolify
# Include devise modules.
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable
include DeviseTokenAuth::Concerns::User
#
# Callbacks
#
after_create :assign_default_role
#
# Attributes
#
attr_accessor :remote_image
#
# Validations
#
validates :name, presence: true, length: {in: 1..100}
validates :last_name, presence: true, length: {in: 1..100}
validates :role_ids, presence: true, on: :update
#
# Relations
#
belongs_to :current_scenario, class_name: "Scenario"
#
# Private Instance Methods
#
def assign_default_role
self.add_role(:user) if self.roles.blank?
end
end
and the following controller code:
def show
#user = User.find(params[:id])
authorize #user
render json: #user, include: ['roles'], status: :ok
end
As you can see, I'm including the roles relationship to be rendered as part of the json api response, with json-api adapter format.
FYI, the UserSerializer:
class UserSerializer < ActiveModel::Serializer
#
# Attributes
#
attributes :id, :email, :name, :last_name, :image_url, :image_thumb_url, :created_at, :updated_at, :current_scenario_id, :last_sign_in_at
#
# Relations
#
has_one :current_scenario
has_many :roles
#
# Methods
#
def image_url
object.image_url
end
def image_thumb_url
object.image_url(:thumb)
end
end
When retrieving the json response, I get the following:
{
"data": {
"id":"2",
"type":"users",
"attributes": {
"email":"talvarez#igaltex.com.ar", ...
},
"relationships": {
"current-scenario": {
"data": {
"id":"204",
"type":"scenarios"
}
},
"roles": {
"data": [
{
"id":1,
"name":"user",
"resource-type":null,
"resource-id":null,
"created-at":"2017-01-23T10:27:08.707-03:00",
"updated-at":"2017-01-23T10:27:08.707-03:00"
},
{
"id":2,
"name":"admin",
"resource-type":null,
"resource-id":null,
"created-at":"2017-01-24T09:40:53.020-03:00",
"updated-at":"2017-01-24T09:40:53.020-03:00"
}
]
}
}
}
}
As you can see, the included relationship roles with all its attributes is inside the relationships fragment of the json-api response. Shouldn't the roles data be inside the included fragment, which by the way is missing? Moreover inside the relationship fragment roles should appear only as a reference like: {relationships: {roles: [{id: "1", type: "role"}, {id: "2", type: "role"}]} am I wrong?
To contrast this, look what happens when also including the current_scenario relationship:
{
"data": {
"id":"2",
"type":"users",
"attributes": {
"email":"talvarez#igaltex.com.ar",
"name":"Tomás",
"last-name":"Alvarez",
...
},
"relationships": {
"current-scenario": {
"data": {
"id":"204",
"type":"scenarios"
}
},
"roles": {
"data": [
{
"id":1,
"name":"user",
"resource-type":null,
...
}
]
}
},
"included": [
{
"id":"204",
"type":"scenarios",
"attributes": {
"name":"Scenario reload II",
"description":null,
"created-at":"2017-04-18T11:55:35.242-03:00",
"updated-at":"2017-04-18T11:55:35.242-03:00"
},
"relationships": {
"scenario-stocks": {
"data":[]
}
}
}
]
}
}
See how now the included fragment appears with all the information about current_scenario and only the reference to current_scenario is added to the relationships fragment. Is this because roles is a has_many relationship in the active model serializer while current_scenario is a belongs_to ? Am I understanding wrong the json-api adapter specification?
Many thanks!
Ouch. The inconsistency in the JSON-API response was because i forgot to add a Role model serializer in the backend side (Rails 5). This is the json response now, which is what i was looking for:
{
"data": {
"id": "2",
"type": "users",
"attributes": {
"email": "talvarez#igaltex.com.ar",
"name": "Tomás",
"last-name": "Alvarez",
"image-url": "http://localhost:3001/uploads/user/image/2/05a4dc7.jpg",
"image-thumb-url": "http://localhost:3001/uploads/user/image/2/thumb_05a4dc7.jpg",
"created-at": "2017-01-23T10:39:12.485-03:00",
"updated-at": "2017-04-25T16:32:14.610-03:00",
"current-scenario-id": 204,
"last-sign-in-at": "2017-04-25T16:29:03.559-03:00"
},
"relationships": {
"current-scenario": {
"data": {
"id": "204",
"type": "scenarios"
}
},
"roles": {
"data": [{
"id": "1",
"type": "roles"
}, {
"id": "2",
"type": "roles"
}]
}
}
},
"included": [{
"id": "204",
"type": "scenarios",
"attributes": {
"name": "Scenario reload II",
"description": null,
"created-at": "2017-04-18T11:55:35.242-03:00",
"updated-at": "2017-04-18T11:55:35.242-03:00"
},
"relationships": {
"scenario-stocks": {
"data": []
}
}
}, {
"id": "1",
"type": "roles",
"attributes": {
"name": "user"
}
}, {
"id": "2",
"type": "roles",
"attributes": {
"name": "admin"
}
}]
}
Sorry for this bug. We haven't figured out how to determine the type for a relationship when no serializer is found. Would it have been more helpful if you got an exception?
This is how JSON API works you can not retrieve desired model + relationship in 'one object'. Relationships always are separated. So you need to 'glue' it somehow. You can use gem that can help you with it, or you can do it on front end side (all big frameworks support it).
In general this approach with 'relationships' looks weird, but when you have complex object with tons of dependencies, this is the only one way that works.
I'm using Ionic framework, but question is about AngularJS. I have written json api on ruby on rails. For authentication I choose ng-token-auth + devise-token-auth.
User json:
{
"data": {
"id": "1",
"type": "users",
"attributes": {
"name": "User1",
"email": "email#email.com",
"current-sign-in-at": "..",
"last-sign-in-at": "..",
"created-at": ".."
},
"relationships": {
"friends": {
"data": [
{
"id": "2",
"type": "users"
}
]
}
}
},
"included": [
{
"id": "2",
"type": "users",
"attributes": {
"name": "User2",
"email": "user#user.com",
"current-sign-in-at": "..",
"last-sign-in-at": "..",
"created-at": ".."
},
"relationships": {
"friends": {
"data": [ ]
}
}
}
]
}
My serializer for user:
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :email,:current_sign_in_at,
:last_sign_in_at, :created_at
has_many :friends, through: :friendships
end
Current user object as response:
{"success":true,"data":{"id":1,"provider":"email","uid":"0a56bb6b-dc72-4ef3-906e-1c17cb2fef46","name"
:"User1","nickname":null,"image":null,"email":"email#email.com"}}
There is my problem, I can't reach user relationship information.
I don't know why, but object name in my view is user (not current_user) and don't have access for it in my controllers.
Question is how can I have some additional information from this object and how to provide current user object access for my controllers.
I figured it out, so the answer is really simple. ng-token-auth provide $rootScope.user as the current user and it can be used in controllers.
But I still don't know how to provide related data to user object, fortunately found workaround for my needs.
I am using Rails 5 and AMS 10. My questions are in bold.
gem 'active_model_serializers', '~> 0.10.0.rc1'
rails -v
Rails 5.0.0.beta3
I am using the JSON API specifications. I have this in an config/initializers/active_model_serializer.rb file:
ActiveModel::Serializer.config.adapter = :json_api
Is the point of this to apply convention to JSON API responses which have total freedom in customization of the nodes? This leads to faster understanding of the data from developers who consume the API for like front-end Ember apps or mobile native apps?
This is my RentalUnitSerializer:
class RentalUnitSerializer < ActiveModel::Serializer
attributes :id, :rooms, :bathrooms, :price, :price_cents
belongs_to :user
end
This is my UserSerializer:
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :email
def name
names = object.name.split(" ")
"#{names[0].first}. #{names[1]}"
end
end
This is my rental_units_controller:
class RentalUnitsController < ApplicationController
before_action :set_rental_unit, only: [:show, :update, :destroy]
# GET /rental_units
def index
#rental_units = RentalUnit.all
render json: #rental_units
end
This is my JSON response when I hit the /rental_units endpoint.
{
"data": [{
"id": "1",
"type": "rental_units",
"attributes": {
"rooms": 2,
"bathrooms": 2,
"price": null,
"price_cents": 50000
},
"relationships": {
"user": {
"data": {
"id": "1",
"type": "users"
}
}
}
}, {
"id": "2",
"type": "rental_units",
"attributes": {
"rooms": 2,
"bathrooms": 2,
"price": null,
"price_cents": 50000
},
"relationships": {
"user": {
"data": {
"id": "1",
"type": "users"
}
}
}
},
How do I get the user's name in the relationships section of the JSON API response?
You need to add .includes(:user) to your query on the first line of your index method in RentalUnitsController, thusly:
#rental_units = RentalUnit.includes(:user).all
That will add the actual data from the :user relationship's target object to your RentalUnit object and to the serialized JSON.
class User < ActiveRecord::Base
has_many :friends
accepts_nested_attributes_for :friends
end
class Friend < ActiveRecord::Base
belongs_to :user
end
A user will keep adding friends via a REST API:
{ "user": {
"name": "Peter",
"friends_attributes": [
{ "name": "Paul" },
{ "name": "Mary" }
]
}
}
Later, the user will add more friends and call the same API:
{ "user": {
"name": "Peter",
"friends_attributes": [
{ "name": "Paul" },
{ "name": "Mary" },
{ "name": "John" }
]
}
}
Now, how should I write the validation such that:
only new friends are added (i.e. John)
without duplicating existing ones (i.e. Paul, Mary)
don't failed the API call as a whole