I am using the ActiveRecord Json Validator Gem and I have the following object thats trying to be saved to the model, which should fail, but instead isnt:
[1] pry(#<Api::Internal::V1::UsersController>)> backup
=> #<UserBackup:0x24eddeed
id: nil,
location_name: "test",
user_params: {"user"=>{"user_name"=>"GeorgeLucas", "email"=>"asdsadasdas", "auth_token"=>"jesus"}, "controller"=>"api/internal/v1/users", "action"=>"create"}>
[2] pry(#<Api::Internal::V1::UsersController>)> backup.save!
=> true
As you can see theres a unsaved UserBackup model object in [1] and in [2] is saves, the json in [1] is missing somethig that is set to required: first_name.
The model looks like such:
class UserBackup < ActiveRecord::Base
PROFILE_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'user.json_schema').to_s
validates :location_name, :presence => true
validates :user_params, :presence => true, json: {schema: PROFILE_JSON_SCHEMA}
end
The Json Schema Looks like:
{
"type": "object",
"$schema": "http://json-schema.org/draft-03/schema",
"properties": {
"user": {
"first_name": {
"type": "string",
"required": true
},
"last_name": {
"type": "string",
"required": false
},
"user_name": {
"type": "string",
"required": true
},
"email": {
"type": "string",
"required": true,
"format": "email"
},
"auth_token": {
"type": "string",
"required": true
}
}
}
}
According to my schema, first_name must be a string and it is a required field.
In my json being saved, its missing entirely. Which means that the model should not save the object to the database.
Update One:
Looking at the model object the user_params doesn't look like a json object at all, it looks like a hash of hashes. So with that in mind, here is the controller:
module Api
module Internal
module V1
class UsersController < BaseController
before_action :restrict_api_access
def create
params_json = params.to_json
backup = UserBackup.new(location_name: from_site, user_params: params_json)
binding.pry
if backup.save
return render json: {}, status: 200
else
return render json: {}, status: 422
end
end
end
end
end
end
params_json is indeed a json object:
[3] pry(#<Api::Internal::V1::UsersController>)> params_json
=> "{\"user\":{\"user_name\":\"GeorgeLucas\",\"email\":\"asdsadasdas\",\"auth_token\":\"jesus\"},\"controller\":\"api/internal/v1/users\",\"action\":\"create\"}"
So I am unsure at this point, what exactly is going on.
Related
app/models/author.rb:
class Author < ApplicationRecord
validates :name, presence: true,
uniqueness: { case_sensitive: false }
end
app/serializers/author_serializer.rb:
class AuthorSerializer < ActiveModel::Serializer
attributes :id, :name, :bio
end
spec/spec_helper.rb:
#...
require 'json_matchers/rspec'
JsonMatchers.schema_root = 'spec/support/api/schemas.author.json':
#...
spec/support/api/schemas/authors/show.json:
{
"id": "file:/authors/show.json#",
"type": "object",
"definitions": {
"authors": {
"description": "A collection of authors",
"example": [{ "id": "1", "name": "Name" }],
"type": "array",
"items": {
"$ref": "file:/author.json#"
}
}
},
"required": ["authors"],
"properties": {
"authors": {
"$ref": "#/definitions/authors"
}
}
}
spec/requests/authors_show_request_pec.rb:
RSpec.describe 'Authors', type: :request do
setup { host! 'api.example.com' }
describe 'GET /author/:id' do
let!(:author) { create(:author, id: 13, name: 'Name', bio: 'bio') }
it 'returns requested author' do
get author_path(13)
expect(response).to have_http_status(200)
author_from_response = JSON.parse(response.body)
expect(author_from_response['name']).to eq(author.name)
expect(author_from_response['bio']).to eq(author.bio)
expect(response).to match_json_schema('author')
end
end
end
Response body contains all expected data, but spec if falinig to validate matching response to json schema.
Json_matchers gem seems to be configured according to manual.
Error that appears:
JsonMatchers::InvalidSchemaError:
783: unexpected token at '}
'
Try removing the trailing commas. The ruby JSON parser does not like them.
Your JSON response should be:
{
"id": "file:/authors/index.json#",
"type": "object",
"definitions": {
"authors": {
"description": "A collection of authors",
"example": [{ "id": "1", "name": "Name" }],
"type": "array",
"items": {
"$ref": "file:/author.json#"
}
}
},
"required": ["authors"],
"properties": {
"authors": {
"$ref": "#/definitions/authors"
}
}
}
Notice no trailing commas after the "items", "authors" nor "properties".
Here it's my products.controller
def index
if params[:page]
#product = Product.page(params[:page]).per(5)
else
#product = Product.order('updated_at DESC')
end
render json: {
status: '200',
message: 'OK',
data: ActiveModel::Serializer::CollectionSerializer.new(#product, each_serializer: ProductSerializer)},status: 200
end
def show
render json: {
status: '200',
message: 'OK',
data: ActiveModel::Serializer::CollectionSerializer.new(#product, each_serializer: ProductSerializer)},status: 200
end
Here my product serializer
class ProductSerializer < AplicationSerializer
attributes :id, :product_name, :category, :company, :description, :logo_img, :web_link
belongs_to :category
has_many :images
en
d
If I try to access localhost:3000/products/, it can display all the data that I want, but if I want to try localhost:3000/products/1, it's wrong, so what the wrong about my code?
{
"status": "200",
"message": "OK",
"data": [
{
"id": 1,
"product_name": "MDS",
"category": {
"id": 4,
"category": "Market Place",
"created_at": "2018-04-12T02:58:59.949Z",
"updated_at": "2018-04-12T02:58:59.949Z"
},
"company": "PT Media Data ",
"description": "ISP di Indo",
"logo_img": "mds.jpg",
"web_link": "https://mds.co/",",
"images": [
{
"id": 1,
"link": "http:/mds.com/mds.png"
},
{
"id": 2,
"link": "http:/mds.com/mds.png"
},
{
"id": 3,
"link": "http:/mds.com/mds.png"
},
{
"id": 4,
"link": "http:/mds.com/mds.png"
}
]
}
]
}
in the above is the display I want to display at http://localhost:3000/products/1, but i still got no appear, even though i can display it with same code on def index
Thanks for your help
The only thing that can seem to be missing here is setting the #product
If you have a before_action :show, :set_product this might not be the problem.
The other difference is between an array in the index and a single element in the show. you better use ProductSerializer.new(#product).as_json instead of using the collection serializer
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 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.
I have to following rabl code to generate some JSON data.
object #event
attributes :id, :EID, :name, :address, :description, :latitude, :longitude, :time, :created_at
node(:rsvp_count) { |event| event.rsvp_users.count }
node(:check_in_count) { |event| event.checkedin_users.count }
node(:FID) { |event| event.creater.FID if event.creater}
child :rsvp_users, :object_root => false do
extends 'users/index'
end
child :checkedin_users, :object_root => false do
extends 'users/index'
end
And the data it generates looks like this:
[
{
"event": {
"id": 2,
"EID": 123458,
"name": "event no.2",
"address": "189 elm st",
"description": "awesome event",
"latitude": 10,
"longitude": 10,
"time": "2013-10-20T18:00:00Z",
"created_at": "2013-08-15T21:06:21Z",
"rsvp_count": 3,
"check_in_count": 0,
"FID": 12345678,
"users": [
{
"id": 4,
"FID": 112233445,
"name": "name1",
"using_app": true
},
{
"id": 3,
"FID": 9999,
"name": "name2",
"using_app": false
},
{
"id": 2,
"FID": 123456789,
"name": "name3-robot",
"using_app": true
}
],
"checkedin_users": []
}
}
]
You can ignore the event hash, the weird stuff is happening at the bottom in the 2 users array.
So as you can see, the child rsvp_users array is showing up with the name users even if I set the root param to "rsvp_users". However, for checkedin_users array (which is empty right now), I don't need to do anything, and it's name is automatically checkedin_users. What is happening here? Is it a bug in rabl? Or is it something that I am missing?
I've encountered the same exact bug, the problem seems to be setting the object_root to false.
Following the comment of Bigxiang I have experimented a bit and found that this works fantastically:
child( {:rsvp => :rsvp}, {:object_root => false} ) do
extends "users/index"
end
Note both the round parentheses "()" and braces "{}".