Using Loopback 4, I want to use the type-safety of Typescript in my application code while getting the schema validation from the repository decorators, but store a property as serialized json (in this case, Postgres bson) instead of as a separate entity with a FK relation.
Consider a address book Contact model that could have a list of phone numbers:
#model()
export class PhoneNumber {
#property({ required: true })
number: string;
#property()
type: string;
}
#model()
export class Contact extends Entity {
#property({ id: true, required: true })
id: string;
#property({ required: true })
email: string;
#property({ required: true })
name: string;
#property.array(PhoneNumber)
phoneNumbers: PhoneNumber[];
constructor(data?: Partial<Contact>) {
super(data);
}
}
In the above example, I get full schema validation, but if I try to save an instance of Contact using a generated Repository that extends DefaultCrudRespository, it just drops whatever was provided in the phoneNumbers field and saves an empty column in the db.
If I change the property annotation to:
#property.array(Object)
phoneNumbers: PhoneNumber[];
It will save the field properly, serialized as json, but it won't attempt to validate the field, and also won't specify the type as an array PhoneNumber in the generated openapi.json spec
It seems that Loopback 3 had support for embedded models: https://loopback.io/doc/en/lb3/Embedded-models-and-relations.html
No mention of it in the Loopback 4 docs, though.
Try use the strict mode filter in the model PhoneNumber
#model({settings: {strict: "filter"}})
This make any unknow field be ignored when add in the database
Related
I have a Department model in my project. The properties are id, name. name of course should be unique. However I can't seem to look where the setting is so I can put something like
#model()
export class Department extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#property({
type: 'string',
required: true,
unique: true // ---> This is what I want. But how?
})
name: string;
constructor(data?: Partial<Department>) {
super(data);
}
}
I tried digging to Model documentation, it seems that there is something I can do with the #model decorator. However I found no documentation about it.
Also, I want to do this in PostgreSQL as a datasource. Any clue? Any advice would be appreciated.
#property({
type: 'string',
required: true,
index: {
unique: true,
}
})
name: string;
Using index object.
Migrate Database - Modify or alter table
run command : npm run build
migrate database : npm run migrate
I have a many-to-many relationship defined between my tag and payment models as shown below.
//models/tag.js
import Model, { attr, hasMany } from '#ember-data/model';
export default Model.extend({
name: attr('string'),
backgroundColour: attr('string'),
textColour: attr('string'),
payments: hasMany('payment')
});
// models/payment.js
import Model, { attr, hasMany } from '#ember-data/model';
export default Model.extend({
date: attr('date'),
amount: attr('number'),
paymentId: attr('string'),
tags: hasMany('tag'),
});
By default, when I add tags to payments, the id of the payment is used as the key for the relationship. My aim is for Ember data to use the paymentId property as the key for this relationship instead.
The snippet below shows the structure of the data I'm loading, where a tag references payments by the paymentId property.
// Example tag
{
"id": "25",
"name": "Groceries",
"backgroundColour": "31b04b",
"textColour": "ffffff",
"payments": ["20190121201902210"] // References paymentId rather than id
},
// Example payment
{
"id": "1"
"date": "2019-01-27T22:00:00.000Z",
"amount": 1644.44,
"paymentId": "20190121201902210",
"tags": ["25"]
}
I've tried to customise the payment serializer as below,
// serializers/payment.js
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
keyForRelationship(key, _relationship) {
if (key === 'payments') {
return 'paymentId';
}
},
});
However, when the models are loaded I get this error: Assertion Failed: All elements of a hasMany relationship must be instances of Model, you passed [ "20190121201902210" ].
How can I make Ember data use paymentId rather than id when looking up related payments?
I assume that you are using RESTSerializer. It has a primaryKey option, which should be used if the primary key is not named id in your API payload.
It seems to be a little bit difficult for your example as that record seems to have two primary keys: id and paymentId. If only one of them is used to reference related records I would recommend to simply ignore the other one.
If both are used to reference related records, you are in a bad situation. Maybe you can change the API?
If that's not possible I guess you need to map one ID to the other in a serializer which requires to have the payment record loaded before. That gets tricky as serializers are sync which means the record must be loaded before. I guess you will face a lot of edge cases until such a solution is stable - and breaking it would be quiet easy as it highly depends on timing.
So maybe you should even consider not using Ember Data for these resources at all. It highly depends on the assumption that each resource could be identified by a combination of it's type and ID. But it sounds like for you the same resource could be identified by two different IDs.
If you don't specify anything all the fields are created as lower case in the database when using postgres. Is it possible to change the default behavior to use the exact name of the fields in the model? That makes it easier to write custom queries.
As it is now I have to configure the property on each field to say that they should be camel case, and that is quite error prone since that is something that is easy to forget.
If that isn't possible, is it possible to use the functionality in the repository that does the mapping from all lowercase to the fields in the models in an easy manner somehow?
Not sure if this helps, but you can use name property
example:
export class User extends .... { #property({
type: 'number',
id: true, }) id?: number;
#property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
#property({
type: 'string',
name: 'last_name',
})
lastName: string;
I have the following models:
App.Publication = DS.Model.extend({
title: DS.attr('string'),
bodytext: DS.attr('string'),
author: DS.belongsTo('author')
});
App.Author = DS.Model.extend({
name: DS.attr('string')
});
And the folowing json data:
{
"publications": [
{
id: '1',
title: 'first title',
bodytext: 'first body',
author_id: 100
},
{
id: '2',
title: 'second title',
bodytext: 'second post',
author_id: 200
}
];
}
In Ember Data RC12 this worked (you could specify author_id OR author in the json and the publication would always have the correct author linked).
In Ember Data 1.0.0 this no longer works; author is always null.
In some documents I found that - since I am using "author_id" in the json data (and not simply author) - I need to specify the key in the model; thus:
author: DS.belongsTo('author', { key: 'author_id' })
This however does not work; the author in the publication remains null.
The only solution I see for now is to implement a custom serializer and override the author_id to author (via normailzeId); I cannot change my backend data structure ... thus:
App.MySerializer = DS.RESTSerializer.extend({
//Custom serializer used for all models
normalizeId: function (hash) {
hash.author = hash.author_id;
delete hash.author_id;
return hash;
}
});
Is the above the correct way ?
Ember Data 1.0 no longer does any payload normalization by default. The key configuration for DS.belongsTo has been removed as well so you will have to implement a custom serializer.
normalizeId is an internal serializer function used for converting primary keys to always be available at id. You shouldn't override this.
Instead, you can override the keyForRelationship method which is provided for this purpose.
You could use something like the following:
App.ApplicationSerializer = DS.RESTSerializer.extend({
keyForRelationship: function(rel, kind) {
if (kind === 'belongsTo') {
var underscored = rel.underscore();
return underscored + "_id";
} else {
var singular = rel.singularize();
var underscored = singular.underscore();
return underscored + "_ids";
}
}
});
Note: I've also renamed the serializer to App.ApplicationSerializer so that it will be used as the default serializer for your application.
Finally, if you haven't already found it, please take a look at the transition notes here: https://github.com/emberjs/data/blob/master/TRANSITION.md
If you read through the transition document shortly after the initial 1.0.0.beta.1 release I would recommend taking a look again as there have been a number of additions, particularly regarding serialization.
From the Ember 1.0.0 Transition Guide:
Underscored Keys, _id and _ids
In 0.13, the REST Adapter automatically camelized incoming keys for you. It also expected belongsTo relationships to be listed under name_id and hasMany relationships to be listed under name_ids.
If your application returns json with underscored attributes and _id or _ids for relation, you can extend ActiveModelSerializer and all will work out of the box.
App.ApplicationSerializer = DS.ActiveModelSerializer.extend({});
Note: DS.ActiveModelSerializer is not to be confused with the ActiveModelSerializer gem that is part of Rails API project. A conventional Rails API project with produce underscored output and the DS.ActiveModelSerializer will perform the expected normalization behavior such as camelizing property keys in your JSON.
does anyone know if there is a recent problem with camelcased properties.
I have a model like this:
var attr = DS.attr;
App.Users = DS.Model.extend({
firstName: attr('string'),
phone: attr('string'),
email: attr('string')
});
In my template email and phone show up correctly but firstName doesnt appear. I checked the json file and everything seems to be fine. With all my other models the same problems appear,
so i guess it has to do something with the camelCase.
Deprecation Notice: This answer is from 2013, and hasn't been touched up until now. This does not reflect JSON API Adapter but rather Ember-Data's RESTAdapter.
Ember.js relies on naming conventions, and it expects that multiple-word-camel-case (e.g. firstName) model properties to be mapped to multiple-word-underscore-separated-lower-case attributes (e.g. first_name) on the JSON response. If your JSON is giving you firstName, or anything that is not in this convention in a scenario that you do not control the backend API, you have the option of defining a map which tells your adapter to look for a specific key in the JSON response and map it to a property in a given Model.
You can do something like this:
DS.RESTAdapter.map('App.User', {
firstName: { key: 'firstName' },
lastName: { key: 'familyName' }
});
Note that in the sample above I've added a lastName property which is not part of your own model. I've done that just to make it clear that you can map several properties at the same time, and its name in the JSON response can be anything, not necessarily the same name in a different casing.