Save nested models using Ember Data - ember.js

I have two models in Ember:
Collection
export default DS.Model.extend({
name: DS.attr(),
description: DS.attr(),
items: DS.hasMany('collection-item')
});
Collection Item
export default DS.Model.extend({
date: DS.attr(),
volume: DS.attr(),
sequenceNumber: DS.attr()
});
I want to save the collection items inside the 'items' attribute of the collection, like MongoDB:
[{
"name": "First Collection",
"description": "This is my first collection",
"items": [
{
"date": "2017-07-26",
"volume": "1",
"sequenceNumber": "1"
},
{
"date": "2017-07-27",
"volume": "1",
"sequenceNumber": "2"
}
]
},
{
"name": "Second Collection",
"description": "This is my second collection",
"items": [
{
"date": "2017-07-26",
"volume": "1",
"sequenceNumber": "1"
},
{
"date": "2017-07-27",
"volume": "1",
"sequenceNumber": "2"
}
]
}]
I have read something about serializers, but I don't get the point ;) Can someone give me a hint?
BTW, I'm using Firebase (emberfire) for now, but I'm going to build my own API in future.

What you're describing is known as an embedded record in Ember. On the serializers page, beneath the discussion of the JSONAPISerializer is a discussion of the embedded record mixin: https://guides.emberjs.com/v2.14.0/models/customizing-serializers/
You can use a RESTSerializer with an embedded mixin to achieve what you're after.
That said, unless your backend needs are fairly simple, I'd suggest beginning to build a backend (and using JSON-API for it) before you get too far. JSON-API is a spec based off of pain points the entire Ember community has felt over the years. If you build a simpler backend right now, you may find yourself hitting headaches in the future that JSON-API is specifically designed to address.
Good luck!

If you are using the defaults from Ember Data, you need to have a JSON API compatible backend service where to retrieve/send data from. You can take a look at the projects implementing JSON API standards if you don't have a backend yet.
After you have a working API, the rest is fairly straightforward and well documented.

Related

Loopback 4 The swagger generated filters don't seem to work

I set up a basic model and controller connected to mysql database. Everything looks good and is working.
My Definition Model looks like this.
import { Entity, model, property } from '#loopback/repository';
#model({
settings: {
mysql: {
table: 'definition'
}
}
})
export class Definition extends Entity {
#property({
type: 'number',
id: true,
generated: true,
forceId: true,
required: false,
description: 'The unique identifier for a definition',
})
id: number;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'string',
required: true,
})
process_id: string;
constructor(data?: Partial<Definition>) {
super(data);
}
}
export interface DefinitionRelations {
// describe navigational properties here
}
export type DefinitionWithRelations = Definition & DefinitionRelations;
When I have it up and running and click on the explorer to test it out.
I click on "Try it out" for get /definitions and the only editable field that gets enabled is the filter field.
It is repopulated with a value like this...
{
"where": {},
"fields": {
"id": true,
"name": true,
"process_id": true
},
"offset": 0,
"limit": 0,
"skip": 0,
"order": [
"string"
]
}
I have to clear that to get it to work which is fine. When I run it with no filter it returns these results.
[
{
"id": 10,
"name": "Place Manual Payoff Order Process Config",
"process_id": "Process_PlaceManualPayoffOrderProcessConfig"
},
{
"id": 11,
"name": "test",
"process_id": "Test"
},
{
"id": 12,
"name": "test2",
"process_id": "test2"
}
]
I am trying to use the filter expression to only return ones with a specific process_id field. So I change the filter to look like this.
{
"where": {"process_id": "test2"}
}
And it still returns the same results.
[
{
"id": 10,
"name": "Place Manual Payoff Order Process Config",
"process_id": "Process_PlaceManualPayoffOrderProcessConfig"
},
{
"id": 11,
"name": "test",
"process_id": "Test"
},
{
"id": 12,
"name": "test2",
"process_id": "test2"
}
]
Do filters currently work in Loopback 4 or am I using them incorrectly?
EDIT: if I post the filters in the URL string they work. It seems as though the openapi ui isn't generating that part of the filter Into the URL string.
It seems as though the openapi ui isn't generating that part of the filter Into the URL string.
Yes, this a precise description.
OpenAPI Spec version 3.x does not specify how to serialize deeply-nested values to URL queries and swagger-js, the library powering swagger-ui (which powers LoopBack's REST API Explorer), silently ignores such values.
The issue has been reported and is being discussed here:
swagger-api/swagger-js#1385
OAI/OpenAPI-Specification#1706

loopback remote method: parameter validation

Is there a form to make loopback automatically validate input parameters in a remote method?
Let's assume we have the following definition of a remote method:
WebuserModel.remoteMethod('overLogin', {
description: "Performs a Webuser's login to the system",
accepts: [
{
arg: 'credentials', type: {
"username": { type: "string", required:true },
"password": { type: "string", required: true }
},
http: {source: 'body'},
required: true
},
],
returns: {arg: 'accesToken', type: "object", root: true},
http: {path: '/login', verb: 'post'}
}
I would here expect from loopback to perform validation of the input parameter on each request and to raise an error if the passed object does not comply with the defined schema (mandatory object with two mandatory properties).
Apparently it does not happen.
Any clue?
Disclaimer: I am a core developer of LoopBack and the author of argument validation in strong-remoting#3.x.
LoopBack does not support validation of nested object properties provided by the clients when invoking remote methods. Right now, we check only that the value is an object, see lib/types/object.js in strong-remoting.
In the upcoming LoopBack 4 version, we are planning to support full OpenAPI and/or JSON Schema validation for input arguments, see https://github.com/strongloop/loopback-next/issues/118
Based on the comments in that GitHub issue, it should be relatively easy to add JSONSchema-based validations to LoopBack 3.x too.

How to get Ember to Lazy (async) Load hasMany

Possibly I'm misunderstanding how Ember wants to lazy load hasMany sides of relationships but I'll specify what I want and someone can tell me if it is possible and how to configure things.
I'm using the RESTAdapter and I have a parent object with a 1-to-many relationship with several other objects. When I view the parent, I have links for the children objects that I want to show as child outlets.
Examples might be:
// brand.js
export default Model.extend({
name: attr('string'),
description: attr('string'),
dateCreated: attr('date'),
lastUpdated: attr('date'),
regions: hasMany('region', {async: true})
});
// region.js
export default Model.extend({
name: attr('string'),
dateCreated: attr('date'),
lastUpdated: attr('date'),
brand: belongsTo()
});
When I access /api/brands/1 I'm returning the following JSON:
{
"brand": {
"id": 1,
"dateCreated": 1466456255539,
"lastUpdated": 1466456255936,
"name": "Some Brand",
"description": "Voluptates odio nemo corrupti",
}
}
I have my route defined like so:
this.route('brand', {path: '/brands/:brand_id'}, function() {
this.route('regions');
});
So, in the brand detail screen, I have an {{outlet}} and when I click on the link for regions, I need to fetch the regions for that brand, and the URL would be /api/brands/1/regions.
Everything works if I return all the data in one big result, and my regions router looks like this:
export default Ember.Route.extend({
model() {
return this.modelFor('brand').get('regions');
}
});
But I don't know what to do so that regions gets lazily fetched correctly from my API. Or if it is even possible. I read up on side loading and currently, my API won't return ID's only for children.
With your example, ember.js does not know, what regions to load. Your JSON has to be either (non-embedded):
{
"brand": {
"id": 1,
"dateCreated": 1466456255539,
"lastUpdated": 1466456255936,
"name": "Some Brand",
"description": "Voluptates odio nemo corrupti",
"regions": ["5", "9"]
}
}
Which then loads regions with the id 5 and 9. Or you could embed them directly:
{
"brand": {
"id": 1,
"dateCreated": 1466456255539,
"lastUpdated": 1466456255936,
"name": "Some Brand",
"description": "Voluptates odio nemo corrupti",
"regions": [{
"id": "5",
"name": "Hanover",
"dateCreated": "2016-09-25 18:39:18",
"lastUpdated": "2016-09-25 18:39:18",
},{
"id": "9",
"name": "Cologne",
"dateCreated": "2016-01-19 11:59:18",
"lastUpdated": "2016-02-22 12:09:58",
}]
}
}
See here for the latter.

Strongloop with Emberjs

Ember Data's REST Adapter accepts the JSON from the server in this format:
Taken from the documentation: http://guides.emberjs.com/v1.10.0/models/the-rest-adapter/
{
"post": {
"id": 1,
"title": "Node is not omakase",
"comments": [1, 2, 3]
},
"comments": [{
"id": 1,
"body": "But is it _lightweight_ omakase?"
},
{
"id": 2,
"body": "I for one welcome our new omakase overlords"
},
{
"id": 3,
"body": "Put me on the fast track to a delicious dinner"
}]
}
Is it possible to have this kind of JSON format back from strongloop?
Remote methods are not the best solution because they are per model, and thus not DRY.
You can make Ember-data compatible with Strongloop's loopback api by using the DS.RESTAdapter with DS.JSONSerializer like this:
// app/adapters/application.js
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://loopback-api-host',
namespace: 'api',
defaultSerializer: 'JSONSerializer'
});
http://emberjs.com/api/data/classes/DS.JSONSerializer.html
"In Ember Data, the logic for communicating with a backend data store lives in the Adapter. Ember Data's Adapter has some built-in assumptions of how a REST API should look. If your backend conventions differ from these assumptions Ember Data makes it easy to change its functionality by swapping out or extending the default Adapter."
http://guides.emberjs.com/v2.0.0/models/customizing-adapters/
Similar question:
Making Loopback API Ember.js compatible
By default the out-of-box restful api endpoints would return something that looks more like:
{
"id": 1,
"title": "Node is not omakase",
"comments": [
{
"id": 1,
"body": "But is it _lightweight_ omakase?"
},
{
"id": 2,
"body": "I for one welcome our new omakase overlords"
},
{
"id": 3,
"body": "Put me on the fast track to a delicious dinner"
}
]
}
But you can use remote methods to do the same work and then massage the data into the way you want it to be returned. http://docs.strongloop.com/display/public/LB/Remote+methods

handling models with embedded objects in the payloads with ember data

I'm trying to determine how best to define my models. I'm trying to represent an asset. Ex: car, tv, house, etc.
Here's the payload coming from my server:
{
"asset": {
"id": "1b0b77a2-28d7-488e-9a6f-8a202c297593",
"created_at": "2015-03-19T09:16:43+0500",
"updated_at": "2015-03-19T09:16:43+0500",
"name": "Flat Screen Tv",
"properties": [
{
"name": "brand",
"value": "Sony",
"public": false,
"family": false,
"friends": false
},
{
"name": "price",
"value": 1000,
"public": false,
"family": false,
"friends": false
}
]
}
}
It needs to get POSTed back in the same format. How can I define this model with ember data? The properties are hanging me up. I thought I could just define a transformer but then noticed that changing values in the transformed data doesn't update the asset's "dirty" flag. I need that dirty flag to be updated when the properties get changed. Any ideas?
// app/transforms/array.js
import Ember from 'ember';
import DS from 'ember-data';
export default DS.Transform.extend({
serialize: function(value) {
return value.toArray();
},
deserialize: function(value) {
return Ember.A(value);
}
});
Here's my asset model definition.
import DS from 'ember-data';
// app/models/asset.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
createdAt: DS.attr('date'),
updatedAt: DS.attr('date'),
properties: DS.attr('array')
});
Your properties are going to have to be a separate model, and related to your asset models with hasMany. If that doesn't work for you, you might have to rethink how you are storing your properties.