I am trying to display some CI information via an ember application backed by Firebase. If I do a this.store.find('plan'); it fetches and displays the plans for the desired project, but it doesn't automatically async fetch the plans like I want. I am not quite sure what I am doing wrong.
DEBUG: -------------------------------
DEBUG: Ember : 1.9.0-beta.1+canary.8105a1bb
DEBUG: Ember Data : 1.0.0-beta.11+canary.d96c747da5
DEBUG: EmberFire : 1.2.7
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery : 1.10.2
DEBUG: -------------------------------
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('project');
}
});
App.ApplicationAdapter = DS.FirebaseAdapter.extend({
firebase: new Firebase('https://my.firebaseio.com/api-data/')
});
App.Project = DS.Model.extend({
name: DS.attr('string'),
plans: DS.hasMany('plan', { async: true })
});
App.Plan = DS.Model.extend({
project: DS.belongsTo('project', { async: true }),
shortName: DS.attr('string'),
shortKey: DS.attr('string'),
type: DS.attr('string'),
enabled: DS.attr('boolean'),
name: DS.attr('string'),
description: DS.attr('string'),
isBuilding: DS.attr('boolean'),
averageBuildTimeInSeconds: DS.attr('number')
});
My template
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each project in model}}
<li>
<h3>{{project.name}}</h3>
<ul>
{{#each plan in project.plans}}
<li>{{plan.name}}</li>
{{else}}
<li>no plans</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</script>
How can I get the ember-data async relationship to automatically fetch when I try to access the project.plans property?
Edit:
I have tried mocking with Ember-CLI's http-mock and am sending back the following for /projects
{"plans":[{id: '10', project: '1', name: 'test', plans: ['10']}]}
Which is now working with adding the plans array. I now just need to figure out how this works on firebase.
When using Firebase after my initial seed data, I fetched each model type and called save on all instances. This caused Firebase to be properly populated for the async data array. This was persisted as follows:
/projects
{
"AS": {
"name": "AS",
"plans": {
"AS-AS": true
}
},
"F": {
"name": "F",
"plans": {
"F-INT": true,
"F-QA": true,
"F-STAG": true
}
}
}
/plans
{
"AS-AS": {
"averageBuildTimeInSeconds": 23,
"description": "",
"enabled": true,
"isBuilding": false,
"name": "AS - AS",
"project": "AS",
"shortKey": "AS",
"shortName": "AS",
"type": "chain"
},
"F-INT": {
"averageBuildTimeInSeconds": 18,
"description": "Integration build",
"enabled": true,
"isBuilding": false,
"name": "F - Integration",
"project": "F",
"shortKey": "INT",
"shortName": "Integration",
"type": "chain"
},
"F-QA": {
"averageBuildTimeInSeconds": 38,
"description": "Release from Stage to QA",
"enabled": true,
"isBuilding": false,
"name": "F - QA",
"project": "F",
"shortKey": "QA",
"shortName": "QA",
"type": "chain"
},
"F-STAG": {
"averageBuildTimeInSeconds": 16,
"description": "Stage Build and Deploy",
"enabled": true,
"isBuilding": false,
"name": "F - Stage",
"project": "F",
"shortKey": "STAG",
"shortName": "Stage",
"type": "chain"
}
}
Related
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
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.
My server returns JSON like below and I cannot change it into sideloading style.
{
"owner": {
"id": 293,
"servers": [{
"id": 32,
"hostname": "host1",
"networks": [ {
"id": 1234,
"ip": 10.10.10.10
}, {
"id": 5678,
"ip": 10.10.20.10
}]
}, {
"id": 33,
"hostname": "host2",
"networks": [ {
"id": 1234,
"ip": 10.10.10.11
}, {
"id": 5678,
"ip": 10.10.20.11
}]
}]
}
}
There is one and only API endpoint for serving above single JSON, and for it, I written three models - owner, server, network - setup like below:
network:
import DS from 'ember-data';
export default DS.Model.extend({
servers: DS.hasMany('server'),
});
server:
import DS from 'ember-data';
export default DS.Model.extend({
hostname: DS.attr('string'),
owner: DS.belongsTo('owner', {async: true}),
networks: DS.hasMany('network', {async: true}),
});
owner:
import DS from 'ember-data';
export default DS.Model.extend({
servers: DS.hasMany('server', {async: true}),
});
Serializer for owner and server like this:
import TestSerializer from './test';
import DS from 'ember-data';
export default TestSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
servers: { embedded: 'always' },
}
});
import TestSerializer from './test';
import DS from 'ember-data';
export default TestSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
networks: { embedded: 'always' },
}
});
when I run it without network-server relation, server-owner relation solved by serializer and it is find. but with 3 level relation, it fails with very long error:
EmberError#http://localhost:4200/assets/vendor.js:27348:15
_emberMetalCore.default.assert#http://localhost:4200/assets/vendor.js:16067:13
ember$data$lib$system$store$serializer$response$$_normalizeSerializerPayloadItem/http://localhost:4200/assets/vendor.js:76803:17
ember$data$lib$system$store$serializer$response$$_normalizeSerializerPayloadItem/http://localhost:4200/assets/vendor.js:76815:35
ember$data$lib$system$store$serializer$response$$_normalizeSerializerPayloadItem/<#http://localhost:4200/assets/vendor.js:76791:12
.eachRelationship/<#http://localhost:4200/assets/vendor.js:87742:11
Map.prototype.forEach/cb#http://localhost:4200/assets/vendor.js:29106:11
OrderedSet.prototype.forEach#http://localhost:4200/assets/vendor.js:28889:11
Map.prototype.forEach#http://localhost:4200/assets/vendor.js:29110:7
.eachRelationship#http://localhost:4200/assets/vendor.js:87741:9
ember$data$lib$system$store$serializer$response$$_normalizeSerializerPayloadItem#http://localhost:4200/assets/vendor.js:76785:7
ember$data$lib$system$store$serializer$response$$_normalizeSerializerPayload#http://localhost:4200/assets/vendor.js:76753:18
ember$data$lib$system$store$$Store<.push#http://localhost:4200/assets/vendor.js:84416:18
ember$data$lib$serializers$embedded$records$mixin$$EmbeddedRecordsMixin<._extractEmbeddedHasMany/<#http://localhost:4200/assets/vendor.js:86708:11
ember$data$lib$serializers$embedded$records$mixin$$EmbeddedRecordsMixin<._extractEmbeddedHasMany#http://localhost:4200/assets/vendor.js:86706:9 ember$data$lib$serializers$embedded$records$mixin$$EmbeddedRecordsMixin<._extractEmbeddedRecords/<#http://localhost:4200/assets/vendor.js:86671:17
.eachRelationship/<#http://localhost:4200/assets/vendor.js:87742:11
Map.prototype.forEach/cb#http://localhost:4200/assets/vendor.js:29106:11
OrderedSet.prototype.forEach#http://localhost:4200/assets/vendor.js:28889:11
Map.prototype.forEach#http://localhost:4200/assets/vendor.js:29110:7
.eachRelationship#http://localhost:4200/assets/vendor.js:87741:9
ember$data$lib$serializers$embedded$records$mixin$$EmbeddedRecordsMixin<._extractEmbeddedRecords#http://localhost:4200/assets/vendor.js:86664:1 ember$data$lib$serializers$embedded$records$mixin$$EmbeddedRecordsMixin<.normalize#http://localhost:4200/assets/vendor.js:86399:16
apply#http://localhost:4200/assets/vendor.js:34198:1
superWrapper#http://localhost:4200/assets/vendor.js:33836:15
ember$data$lib$serializers$rest$serializer$$RESTSerializer<.extractArray/normalizedArray<#http://localhost:4200/assets/vendor.js:77580:20
ember$data$lib$serializers$rest$serializer$$RESTSerializer<.extractArray#http://localhost:4200/assets/vendor.js:77579:33
ember$data$lib$serializers$json$serializer$$JSONSerializer<.extractFindAll#http://localhost:4200/assets/vendor.js:74872:16
ember$data$lib$serializers$json$serializer$$JSONSerializer<.extract#http://localhost:4200/assets/vendor.js:74856:16
ember$data$lib$system$store$serializer$response$$normalizeResponseHelper#http://localhost:4200/assets/vendor.js:76729:33
ember$data$lib$system$store$finders$$_findAll/http://localhost:4200/assets/vendor.js:79011:25
Backburner.prototype.run#http://localhost:4200/assets/vendor.js:10843:18
ember$data$lib$system$store$$Store<._adapterRun#http://localhost:4200/assets/vendor.js:84741:16
ember$data$lib$system$store$finders$$_findAll/<#http://localhost:4200/assets/vendor.js:79010:9
tryCatch#http://localhost:4200/assets/vendor.js:67710:14
invokeCallback#http://localhost:4200/assets/vendor.js:67725:15
publish#http://localhost:4200/assets/vendor.js:67693:9
#http://localhost:4200/assets/vendor.js:44051:7
Queue.prototype.invoke#http://localhost:4200/assets/vendor.js:11571:9
Queue.prototype.flush#http://localhost:4200/assets/vendor.js:11635:11
DeferredActionQueues.prototype.flush#http://localhost:4200/assets/vendor.js:11436:11
Backburner.prototype.end#http://localhost:4200/assets/vendor.js:10725:9
Backburner.prototype.run#http://localhost:4200/assets/vendor.js:10847:13
run#http://localhost:4200/assets/vendor.js:31688:12
ember$data$lib$adapters$rest$adapter$$RestAdapter<.ajax/http://localhost:4200/assets/vendor.js:72892:15
jQuery.Callbacks/fire#http://localhost:4200/assets/vendor.js:3346:10
jQuery.Callbacks/self.fireWith#http://localhost:4200/assets/vendor.js:3458:7
done#http://localhost:4200/assets/vendor.js:9512:5
.send/callback#http://localhost:4200/assets/vendor.js:9916:8
vendor.js:44090:9
So, Can I use EmbeddedRecordsMixin for it? or how can I get right result on this condition?
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.
In the Ember tutorial, the many-to-many relationship is introduced as:
App.Post = DS.Model.extend({
tags: DS.hasMany('App.Tag')
});
App.Tag = DS.Model.extend({
posts: DS.hasMany('App.Post')
});
Is it possible to just put 1 of these DS.hasMany relationships. For example (let me know if my code is incorrect in any way):
App.Post = DS.Model.extend({
postid: DS.attr('number'),
content: DS.attr('string'),
tags: DS.hasMany('App.Tag')
});
App.Tag = DS.Model.extend({
tagid: DS.attr('number'),
name: DS.attr('string')
});
So that can have a JSON like in these fixture adapter setup:
App.Tag.FIXTURES = [{
tagid: 1111,
name: 'Ember.js',
}, {
tagid: 2222,
name: 'Javascript'
}];
App.Post.FIXTURES = [{
postid: 3000,
content: 'A JS Question'
tags: [2222]
}, {
postid: 4000,
content: 'An Ember.js Question',
tags: [1111, 2222]
}];
So basically the many-to-many relationship is just established in the parent, that's why I didn't include posts: DS.hsaMany('App.Post') in the App.Tag model.
Is what I'm doing okay? If so, when should I need DS.hasMany in both models?
If not, please correct the Fixture JSON as well.
If the type of adapter makes a difference, please also explain how they're different (related question).
Update: Since intuitivepixel clarified with me that relationship must be many-to-many, let me try it again:
App.Post = DS.Model.extend({
postid: DS.attr('number'),
content: DS.attr('string'),
tags: DS.hasMany('App.Tag')
});
App.Tag = DS.Model.extend({
tagid: DS.attr('number'),
name: DS.attr('string'),
posts: DS.hasMany('App.Post')
});
Can the Fixture adapter setup be like this, where the relationship is ONLY defined in the posts?
App.Tag.FIXTURES = [{
tagid: 1111,
name: 'Ember.js',
}, {
tagid: 2222,
name: 'Javascript'
}];
App.Post.FIXTURES = [{
postid: 3000,
content: 'A JS Question'
tags: [2222]
}, {
postid: 4000,
content: 'An Ember.js Question',
tags: [1111, 2222]
}];
Or does it have to be like this, where the relationship is defined in both. If so, wouldn't the information be redundant? Redundancy is terrible though. :(
App.Tag.FIXTURES = [{
tagid: 1111,
name: 'Ember.js',
posts: [4000]
}, {
tagid: 2222,
name: 'Javascript',
posts: [3000, 4000]
}];
App.Post.FIXTURES = [{
postid: 3000,
content: 'A JS Question'
tags: [2222]
}, {
postid: 4000,
content: 'An Ember.js Question',
tags: [1111, 2222]
}];
Is it possible to just put 1 of these DS.hasMany relationships.
Yes, but this would be then a one-to-many relationship.
So basically the many-to-many relationship is just established in the parent, that's why I didn't include posts: DS.hasMany('App.Post') in the App.Tag model.
What you are trying to do doesn't work, you need then a one-to-many relationship and your models should look like:
App.Post = DS.Model.extend({
postid: DS.attr('number'),
content: DS.attr('string'),
tags: DS.hasMany('App.Tag')
});
App.Tag = DS.Model.extend({
tagid: DS.attr('number'),
name: DS.attr('string')
post: DS.belongsTo('App.Post')
});
And the correspondent FIXTURES then:
App.Tag.FIXTURES = [{
tagid: 1111,
name: 'Ember.js',
post: 4000
}, {
tagid: 2222,
name: 'Javascript',
post: 4000
}];
App.Post.FIXTURES = [{
postid: 4000,
content: 'An Ember.js Question',
tags: [1111, 2222]
}];
Is what I'm doing okay? If so, when should I need DS.hasMany in both models?
It's not really ok, IMHO (and because of the nature of a tag) it should be possible to assign a tag to many posts, and many tags could be assigned to a post. So we end up using a many-to-many relationship like in the ember tutorial you mentioned, there is a reason for that I guess.
So the final answer to your question: can i set up a many-to-many relationship on just-one side in ember-js would be - No!
Update
I've put togheter a small example how a many-to-many relationship would work, see here.
Hope it helps.
You can do it using the RESTAdapter. That way, you are loading the tags alongside with the posts. But as intuitivepixel correctly points out, this is not a truly many-to-many relationship.
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Post.find();
}
});
App.Adapter = DS.RESTAdapter.extend();
App.Adapter.map('App.Post', {
tags: { embedded: 'load' }
});
App.Store = DS.Store.extend({
adapter: App.Adapter.create({
namespace: 'api'
})
});
App.Post = DS.Model.extend({
content: DS.attr('string'),
tags: DS.hasMany('App.Tag')
});
App.Tag = DS.Model.extend({
name: DS.attr('string')
});
And your JSON payload would be:
{
"posts": [
{
"id": 4000,
"content": "An Ember.js Question",
"tags": [
{
"id": 1,
"name": "ember.js"
},
{
"id": 2,
"name": "javascript"
},
{
"id": 3,
"name": "ember-data"
}
]
}
]
}