I have a rails back-end, serving data as per the JSON API standards (jsonapi-resources gem). I have two models: Contact and PhoneNumber.
class Contact < ApplicationRecord
has_many :phone_numbers
end
class PhoneNumber < ApplicationRecord
belongs_to :contact
end
This is the JSON response for the API endpoint.
{
"data": [
{
"id": "6",
"type": "phone-numbers",
"links": {
"self": "http://localhost:3000/phone-numbers/6"
},
"attributes": {
"name": "byeworld",
"phone-number": "9212098"
},
"relationships": {
"contact": {
"links": {
"self": "http://localhost:3000/phone-numbers/6/relationships/contact",
"related": "http://localhost:3000/phone-numbers/6/contact"
}
}
}
}
]
}
These are my Ember models:
for contact:
import DS from 'ember-data';
export default DS.Model.extend({
nameFirst: DS.attr('string'),
nameLast: DS.attr('string'),
email: DS.attr('string'),
twitter: DS.attr('string'),
phoneNumbers: DS.hasMany('phone-number', {async: true, inverse: 'contact'})
});
for phone-number:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
phoneNumber: DS.attr('string'),
contact: DS.belongsTo('contact', {async: true, inverse: 'phoneNumbers'})
});
This is my route handler:
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('contact', params.contact_id, {include: "phone-numbers"});
},
(...)
});
I am unable to access the phone-numbers for a contact on the template by doing:
{{#each model.phoneNumbers as |phone|}}
{{phone.name}}
{{/each}}
Also, the data for the phone-numbers exists in the ember console. What am I missing?
There is open known issue in ember-data version 2.14.3. so please downgrade your ember-data version to 2.13.2 . that might solve your issue.
Refer this ember-data open issue - https://github.com/emberjs/data/issues/4942
model.PhoneNumbers => model.phoneNumbers
If this is not just this typo, try extending your model serializer / adapter from the JSON API adapters / serializers (or do it on you application serializer / adapter if all your models use it):
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({});
and:
import JSONAPISerializer from 'ember-data/serializers/json-api';
export default JSONAPISerializer.extend({});
Related
I am new to API's and Angular and I'm running into an issue I can't seem to resolve.
For a project I am currently working on I have set up a simple Rails API with the fast_jsonapi. This formats my JSON responses to be in line with JSON:API. I then have an Angular app which should take in that response and map it with the Angular2-jsonapi module.
For this issue imagine we have roles and accountabilities. One role can has 0 to many accountabilities. An accountability belongs to a single role.
When I sent a request to return all the roles, I get a JSON response which then gets properly mapped to models by the module. The issue arises when I try to include relationships.
According to the readme I have to define which relationship to include in the .findAll method like so:
{ include: 'accountabilities' }
The correct data is requested from the rails API, as it is the same request which I tested with Postman earlier. However the error that appears in my browser console is as follows:
{message: "parseHasMany - Model type for relationship accountability not found."}
I have tried other relationships and went through the setup proces a couple of times now, but I fail to see where things are going wrong. Searches on Google, Github and Stackoverflow haven't helped me in this case. Any help is very much appreciated.
Here is the relevant code. If any other information or code is needed, let me know and I'll happily update this post.
Example JSON response:
{
"data": [
{
"id": "1",
"type": "role",
"attributes": {
"name": "Farming Engineer",
"purpose": "Iure voluptatum rem dolores.",
"created_at": "2020-01-16T18:38:26.151Z",
"updated_at": "2020-01-16T18:38:26.151Z"
},
"relationships": {
"accountabilities": {
"data": [
{
"id": "6",
"type": "accountability"
},
{
"id": "12",
"type": "accountability"
}
]
}
}
},
{
"id": "2",
"type": "role",
"attributes": {
"name": "IT Supervisor",
"purpose": "Iusto fuga fugiat qui.",
"created_at": "2020-01-16T18:38:26.161Z",
"updated_at": "2020-01-16T18:38:26.161Z"
},
"relationships": {
"accountabilities": {
"data": []
}
}
}
],
"included": [
{
"id": "6",
"type": "accountability",
"attributes": {
"description": "penetrate the market",
"created_at": "2020-01-16T18:38:26.480Z",
"updated_at": "2020-01-16T18:38:26.480Z"
},
"relationships": {
"role": {
"data": {
"id": "1",
"type": "role"
}
}
}
},
{
"id": "12",
"type": "accountability",
"attributes": {
"description": "immersive experience",
"created_at": "2020-01-16T18:38:26.507Z",
"updated_at": "2020-01-16T18:38:26.507Z"
},
"relationships": {
"role": {
"data": {
"id": "1",
"type": "role"
}
}
}
}
]
}
role.model.ts
import {
JsonApiModelConfig,
JsonApiModel,
Attribute,
HasMany,
BelongsTo
} from 'angular2-jsonapi';
import { Accountability } from '#app/shared/models/accountability.model';
#JsonApiModelConfig({
type: 'roles'
})
export class Role extends JsonApiModel {
#Attribute()
name: string;
#Attribute()
purpose: string;
#Attribute({ serializedName: 'created_at' })
createdAt: Date;
#Attribute({ serializedName: 'updated_at' })
updatedAt: Date;
#HasMany()
accountabilities: Accountability[];
}
accountability.model.ts
import {
JsonApiModelConfig,
JsonApiModel,
Attribute,
BelongsTo
} from 'angular2-jsonapi';
import { Role } from '#app/shared/models/role.model';
#JsonApiModelConfig({
type: 'accountabilities'
})
export class Accountability extends JsonApiModel {
#Attribute()
description: string;
#Attribute({ serializedName: 'created_at' })
createdAt: Date;
#Attribute({ serializedName: 'updated_at' })
updatedAt: Date;
#BelongsTo()
role: Role;
}
datastore.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import {
JsonApiDatastoreConfig,
JsonApiDatastore,
DatastoreConfig
} from 'angular2-jsonapi';
import { Role } from '#app/shared/models/role.model';
import { Accountability } from '#app/shared/models/accountability.model';
const config: DatastoreConfig = {
baseUrl: 'http://localhost:3000/api',
apiVersion: 'v1',
models: {
roles: Role,
accountabilities: Accountability
}
};
#Injectable()
#JsonApiDatastoreConfig(config)
export class Datastore extends JsonApiDatastore {
constructor(http: HttpClient) {
super(http);
}
}
role.component.ts
import { Component, OnInit } from '#angular/core';
import { Datastore } from '#app/datastore';
import { Role } from '#app/shared/models/role.model';
import { JsonApiQueryData } from "angular2-jsonapi";
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.sass']
})
export class HomeComponent implements OnInit {
roles: Role[];
constructor(private datastore: Datastore) { }
ngOnInit() {
this.datastore
.findAll(Role, { include: 'accountabilities' })
.subscribe((roles: JsonApiQueryData<Role>) => {
console.log('>>>>>>>>>>>>>', roles);
console.log('>>>>>>>>>>>>>', roles.getModels());
this.roles = roles.getModels();
});
}
}
With a lot of help over on this Github topic I finally managed to get this resolved.
What initially caused the issue is that the response that I got from my API listed the type in relationships and included in singular whereas they needed to be plural.
Adding record_type: :accountabilities to the has_many relationship in my serializer changed this and successfully mapped the response to objects.
I have a Role Entity and a Route Entity which is tree structure, and they're ManyToMany relation.
Now I want to retrive all the roles via RoleRepository.find({relations: ['routes']}), which will load all the roles data as expected, however the routes prop won't automaticlly load it's children data, which looks like:
[{
id: 1,
name: 'route1',
routes: [{
id: 1,
path: '/home'
}]
}]
I've checked all documentation and had no clue to make it.
#Entity()
export class Role {
#PrimaryGeneratedColumn()
public id: number
... some other columns
#ManyToMany(type => Route, route => route.roles)
public routes: Route[]
}
#Entity()
#Tree('materialized-path')
export class Route {
#PrimaryGeneratedColumn()
public id: number
#TreeParent()
public parent: Route
#TreeChildren({ cascade: true })
public children: Route[]
#ManyToMany(type => Role, role => role.routes)
#JoinTable()
public roles: Role[]
}
I think the correct way to get the relations when is a tree entity is making:
await getManager().getTreeRepository(Route).findTrees({ relations: ["roles"] });
To load the children of a relation you can just add the property you want within the relations array like this:
RoleRepository.find({relations: ['routes', 'routes.roles']})
it should give you something like that:
"route": {
"id": 1,
"name": "route1",
"roles": [{
"id": 1,
"name": "role1",
"routes": [{
"id": 1,
"name": "route1"
}, {
"id": 2,
"name": "route2"
}
]
}
]
}
In the context of a remote method, I'm trying to define a model schema of a parameter passed in the body. This object looks like this:
{
name: "Alex",
credentials: {
user: "alex",
pass: "pass"
}
}
So, I have this code in my remote method definition:
MyModel.remoteMethod("postSomething", {
accepts: [
{arg: 'person', type: {
"name": "string",
"credentials": {
"type": "object",
"properties": {
"user": "string",
"pass: "string"
}
}
}, http: {source: 'body'}, required: true
}
],
.....
Unfortunatelly, the details of this embedded object (credentials) are not shown in the generated Swagger explorer. This is what I see:
{
"user": "string",
"credentials": {}
}
I've tried many different ways but I could not show the properties of the credentials object.
Any ideas?
Loopback 2.x
Edit: Note the following only works for Loopback 2.x, as the type registry changed in 3.x.
The problem is that the data you are providing needs to be on the type property for the nested value. This should work:
MyModel.remoteMethod('postSomething', {
accepts: [
{
arg: 'person',
type: {
name: 'string',
credentials: {
type: {
user: 'string',
pass: 'string'
}
}
},
http: {
source: 'body'
},
required: true
}
],
//...
This also works with arrays:
accepts: [
{
arg: 'Book',
type: {
title: 'string',
author: 'string',
pages: [{
type: {
pageNo: 'number',
text: 'string'
}
}]
}
}
],
// ...
Loopback 3.x
Since the model registry and strong remoting changed in Loopback 3.x to only allow string or array types, you can't really avoid creating a new model. If you would like to quickly 'inline' a model without going through the full process of adding the model json file, adding it to model-config.json etc. you can register it directly on the app:
app.registry.createModel('Person', {
firstName: 'string',
lastName: 'string'
}, { base: 'Model' });
You can set the base to one of your other models if you want to extend an existing model (e.g, add another property that is only accepted in the given remote method)
If you want to create the model without cluttering up your model registry, you can do so by calling createModel on loobpack itself:
const loopback = require('loopback')
const modl = loopback.createModel({
name: 'Person',
base: null,
properties: {
firstName: {
type: 'string',
id: true // means it won't have an id property
}
}
});
In both of the above examples, you refer to the model by name to attach it to the remote method:
accepts: [
{
arg: 'Person',
type: 'Person'
}
],
// ...
Note you will need to create a sub-model for every sub-property (e.g. credentials)
Loopback swagger only picks up the outer object ignoring the properties of the object.
If you want to show a nested object in the swagger docs for the request body, you will have to make nested model.
Assuming you have a model called as person. You have to create another model named "credentials" having properties user and password. Then define the relationship in your person model's config
{
"name": "Person",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"credentials": {
"type": "embedsOne",
"model": "credentials",
"property": "credentials",
"options": {
"validate": true,
"forceId": false
}
}
},
"acls": [],
"methods": {}
}
And add a reference to this model where you define your remote method
MyModel.remoteMethod("postSomething", {
accepts: [
{arg: 'person', type: {Person},
http: {source: 'body'}, required: true
}
],
To avoid "Treating unknown remoting type" warning make sure your model is marked as "public" inside your "model-config.json"
I'm working on my second Ember project, and first using Rails on the backend. I'm struggling with loading associated data through my API in a nested route. The association is simple: a folder has many media_files in my backend (I'm aware snake case is against convention but trying to work around it).
When I do the following, I get the correctly nested route (folders/show/media_files) and no complaints when loading the template, but the data is empty in the console and doesn't render in the template.
Thank you for your time.
Here is my routing:
Router.map(function() {
this.route('folders', function(){
this.route('show', {
path: ':folder_id'
}, function() {
this.route('media_files', {resetNamespace: true}, function (){
});
});
});
});
Here are my associations:
Folder:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
media_files: DS.hasMany('media_file')
});
Media Files:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
duration: DS.attr('number'),
createdAt: DS.attr('date'),
folder: DS.belongsTo('folder')
});
Here is the call to the media files index:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
return this.modelFor("folders/show").get("media_files");
}
});
My URL is folders/folder_id/media_files, same as in my API. The JSON there looks like this:
{
media_files: [
{
id: 513009,
url: null,
project_id: 999,
batch_id: 1268,
duration: 30556,
rush: false.....
I connect Ember to a Rails API which delivers some JSON.
I'm trying to get relations working (product hasMany images) but it keeps giving me this error:
Cannot read property 'typeKey' of undefined
My Models:
App.Product = DS.Model.extend({
images: DS.hasMany('image'),
title: DS.attr('string'),
});
App.Image = DS.Model.extend({
product: DS.belongsTo('product')
});
Rails renders json as:
{
"products":[
{
"id": 1,
"title": "product title",
"images":[
{
"id": 1,
"urls":
{
"thumb":"http://domain.com/thumb/image.jpg",
"original":"http://domain.com/original/image.jpg"
}
}
]
}
]
}
Turns out I needed to "sideload" my images in Rails, so the JSON became:
{
"products":[
{
"id": 1,
"title": "product title",
"image_ids": [1]
}
],
"images":[
{
"id": 1,
"urls":
{
"thumb":"http://domain.com/thumb/image.jpg",
"original":"http://domain.com/original/image.jpg"
}
}
]
}
Rails' ProductSerializer:
class ProductSerializer < ActiveModel::Serializer
embed :ids, :include => true
attributes :id, :title
has_many :images
methods :image_urls
end
It seems that you were using embedded JSON in your example. You need to use the EmbeddedRecordsMixin https://github.com/emberjs/data/blob/master/packages/ember-data/lib/serializers/embedded_records_mixin.js and set the appropriate flag to mark images as being embededd