Ember Data: Saving relationships - json

I need to save a deep object to the server all at once and haven't been able to find any examples online that use the latest ember data (1.0.0-beta.4).
For example, with these models:
(jsfiddle)
App.Child = DS.Model.extend({
name: DS.attr('string'),
age: DS.attr('number'),
toys: DS.hasMany('toy', {async:true, embedded:'always'}),
});
App.Toy = DS.Model.extend({
name: DS.attr('string'),
child: DS.belongsTo('child')
});
And this code:
actions: {
save: function(){
var store = this.get('store'),
child, toy;
child = store.createRecord('child', {
name: 'Herbert'
});
toy = store.createRecord('toy', {
name: 'Kazoo'
});
child.set('toys', [toy]);
child.save();
}
}
It only saves the JSON for the child object but not any of the toys -- not even side loaded:
{
child: {
age: null
name: "Herbert"
}
}
Do I have to manually save the toys too? Is there anyway that I can have it send the following JSON to the server:
{
child: {
age: null
name: "Herbert",
toys: [{
name: "Kazoo"
}]
}
}
Or
{
child: {
age: null
name: "Herbert",
toys: [1]
}
}
See JSFiddle: http://jsfiddle.net/jgillick/LNXyp/2/

The answers here are out of date. Ember Data now supports embedded records, which allows you to do exactly what you're looking to do, which is to get and send the full object graph in one big payload. For example, if your models are set up like this:
App.Child = DS.Model.extend({
name: DS.attr('string'),
age: DS.attr('number'),
toys: DS.hasMany('toy')
});
App.Toy = DS.Model.extend({
name: DS.attr('string'),
child: DS.belongsTo('child')
});
You can define a custom serializer for your Child model:
App.ChildSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
toys: {embedded: 'always'}
}
});
This tells Ember Data that you'd like 'toys' to be included as part of the 'child' payload. Your HTTP GET response from your API should look like this:
{
"child": {
"id": 1,
"name": "Todd Smith",
"age": 5,
"toys": [
{"id": 1, "name": "boat"},
{"id": 2, "name": "truck"}
]
}
}
And when you save your model, Ember Data will send this to the server:
{
"child":{
"name":"Todd Smith",
"age":5,
"toys":[
{
"id":"1",
"name":"boat",
"child":"1"
},
{
"id":"2",
"name":"truck",
"child":"1"
}
]
}
}
Here is a JSBin that demonstrates this.
http://emberjs.jsbin.com/cufaxe/3/edit?html,js,output
In the JSbin, when you click the 'Save' button, you'll need to use the Dev Inspector to view the request that's sent to the server.

toys can't be both async and embedded always, those are contradicting options. Embedded only exists on the active model serializer currently.
toys: DS.hasMany('toy', {embedded:'always'})
the toys are a ManyToOne relationship, and since the relationship exists on the belongsTo side it is more efficient to save the relationship during the toy's save. That being said, if you are creating it all at once, then want to save it in one big chunk that's where overriding comes into play.
serializeHasMany: function(record, json, relationship) {
var key = relationship.key;
var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany' ||
relationshipType === 'manyToOne') {
json[key] = get(record, key).mapBy('id');
// TODO support for polymorphic manyToNone and manyToMany relationships
}
},
And your save should be like this
var store = this.get('store'),
child, toy;
child = store.createRecord('child', {
name: 'Herbert'
});
toy = store.createRecord('toy', {
name: 'Kazoo'
});
child.get('toys').pushObject(toy);
child.save().then(function(){
toy.save();
},
function(err){
alert('error', err);
});

I needed a deep object, instead of a side-loaded one, so based on kingpin2k's answer, I came up with this:
DS.JSONSerializer.reopen({
serializeHasMany: function(record, json, relationship) {
var key = relationship.key,
property = Ember.get(record, key),
relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
if (property && relationshipType === 'manyToNone' || relationshipType === 'manyToMany' ||
relationshipType === 'manyToOne') {
// Add each serialized nested object
json[key] = [];
property.forEach(function(item, index){
json[key].push(item.serialize());
});
}
}
});
Now when you call child.serialize(), it will return this object:
{
child: {
name: "Herbert",
toys: [
{
name: 'Kazoo'
}
]
}
}
Which is what I need. Here's the jsfiddle with it in action: http://jsfiddle.net/jgillick/LNXyp/8/

Related

Segregate and arrange data in specific format in Angular?

Hi I am developing Angular 5 application. I am trying to arrange data in specific format. I have json data. I want to convert it to specific format.
Below is the specific format.
this.nodes = [
{
name: 'root1',
children: [
{ name: 'child1' }
]
},
{
name: 'root2',
hasChildren: true
},
{
name: 'root3'
}
];
Below is my data.
{
"userid":"e75792f8-cfea-460e-aca2-07a778c92a7c",
"tenantid":"00000000-0000-0000-0000-000000000000",
"username":"karthik",
"emailaddress":"john#krsars.onmicrosoft.com",
"isallowed":false,
"userroles":[
{
"userroleid":"b81e63d1-09da-4aa0-af69-0f086ddb20b4",
"userid":"e75792f8-cfea-460e-aca2-07a778c92a7c",
"roleid":"85d2f668-f523-4b64-b177-b1a78db74234",
"tenantappid":1,
"validfrom":"2018-01-24T00:00:00",
"validto":"2018-01-24T00:00:00",
"isactive":true,
}
]
}
From the above data, I am trying to convert. From the above data each key/value pair I am converting it to format above given, For example, "userid":"e75792f8-cfea-460e-aca2-07a778c92a7c" I want to make it as
{
name: 'userid',
children: [
{ name: 'e75792f8-cfea-460e-aca2-07a778c92a7c' }
]
}
So below I is my code.
for (let key in results) {
if(results[key] instanceof Array){
this.nodes+=
name:key,
hasChildren: true
}+"}"
}
else
{
this.nodes+="{"+name=key,
children: [
{ name: results[key] }
]+"}"
}
}
Finally When i tried to display my data in console.
console.log(this.nodes);
Above my code does not work. Can someone help me to make this work? Any help would be appreciated. Thank you.
Here is a working example. Just to show you which way to go:
doIt() {
let results = JSON.parse('{"userid":"e75792f8-cfea-460e-aca2-07a778c92a7c","tenantid":"00000000-0000-0000-0000-000000000000","username":"karthik","emailaddress":"john#krsars.onmicrosoft.com","isallowed":false,"userroles":[{"userroleid":"b81e63d1-09da-4aa0-af69-0f086ddb20b4","userid":"e75792f8-cfea-460e-aca2-07a778c92a7c","roleid":"85d2f668-f523-4b64-b177-b1a78db74234","tenantappid":1,"validfrom":"2018-01-24T00:00:00","validto":"2018-01-24T00:00:00","isactive":true}]}');
const nodes = [];
for (const key in results) {
if (results[key] instanceof Array) {
const containerTyp2 = {name: '', hasChildren: false};
containerTyp2.name = key;
containerTyp2.hasChildren = true;
nodes.push(containerTyp2);
} else {
const object = {name: ''};
const containerTyp1 = {name: '', children: []};
object.name = key;
containerTyp1.name = key;
containerTyp1.children.push(object);
nodes.push(containerTyp1);
}
}
console.log('nodes: ', nodes);
}

Output / show / print Meteor Simple Schema of Entities

I have a Meteor app and generated some DB Collections which have a SimpleSchema https://github.com/aldeed/simple-schema-js attached.
Cards = new Mongo.Collection('cards');
Cards.attachSchema(new SimpleSchema({
title: {
type: String,
},
archived: {
type: Boolean,
autoValue() {
if (this.isInsert && !this.isSet) {
return false;
}
},
},
completed: {
type: Boolean,
autoValue() {
if (this.isInsert && !this.isSet) {
return false;
}
},
},
And so on.
Is there a function something like: log( Cards.schema ) which outputs all the defined properties / fields and their datatypes?
Yes! you can do as below at the client side, at the place you have subscribed the Cards collection.
e.g.
Template.xyz.onRendered(function(){
console.log(Cards._c2._simpleSchema);
});

Merging 2 REST endpoints to a single GraphQL response

New to graphQL, I'm Using the following schema:
type Item {
id: String,
valueA: Float,
valueB: Float
}
type Query {
items(ids: [String]!): [Item]
}
My API can return multiple items on a single request of each type (A & B) but not for both, i.e:
REST Request for typeA : api/a/items?id=[1,2]
Response:
[
{"id":1,"value":100},
{"id":2,"value":30}
]
REST Request for typeB : api/b/items?id=[1,2]
Response:
[
{"id":1,"value":50},
{"id":2,"value":20}
]
I would like to merge those 2 api endpoints into a single graphQL Response like so:
[
{
id: "1",
valueA: 100,
valueB: 50
},
{
id: "2",
valueA: 30,
valueB: 20
}
]
Q: How would one write a resolver that will run a single fetch for each type (getting multiple items response) making sure no unnecessary fetch is triggered when the query is lacking the type i.e:
{items(ids:["1","2"]) {
id
valueA
}}
The above example should only fetch api/a/items?id=[1,2] and the graphQL response should be:
[
{
id: "1",
valueA: 100
},
{
id: "2",
valueA: 30
}
]
So I assumed you are using JavaScript as the language. What you need in this case is not to use direct query, rather use fragments
So the query would become
{
items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
}
}
Next in the resolver we need to access these fragments to find the fields which are part of the fragment and then resolve the data based on the same. Below is a simple nodejs file with same
const util = require('util');
var { graphql, buildSchema } = require('graphql');
var schema = buildSchema(`
type Item {
id: String,
valueA: Float,
valueB: Float
}
type Query {
items(ids: [String]!): [Item]
}
`);
var root = { items: (source, args, root) => {
var fields = root.fragments.data.selectionSet.selections.map(f => f.name.value);
var ids = source["ids"];
var data = ids.map(id => {return {id: id}});
if (fields.indexOf("valueA") != -1)
{
// Query api/a/items?id=[ids]
//append to data;
console.log("calling API A")
data[0]["valueA"] = 0.12;
data[1]["valueA"] = 0.15;
}
if (fields.indexOf("valueB") != -1)
{
// Query api/b/items?id=[ids]
//append to data;
console.log("calling API B")
data[0]["valueB"] = 0.10;
data[1]["valueB"] = 0.11;
}
return data
},
};
graphql(schema, `{items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
}
`, root).then((response) => {
console.log(util.inspect(response, {showHidden: false, depth: null}));
});
If we run it, the output is
calling API A
{ data:
{ items: [ { id: '1', valueA: 0.12 }, { id: '2', valueA: 0.15 } ] } }
If we change the query to
{
items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
valueB
}
}
The output is
calling API A
calling API B
{ data:
{ items:
[ { id: '1', valueA: 0.12, valueB: 0.1 },
{ id: '2', valueA: 0.15, valueB: 0.11 } ] } }
So this demonstrates how you can avoid call for api A/B when their fields are not needed. Exactly as you had asked for

Redux: display single record but json is an array

My react-redux app is getting a single record in JSON but the record is an array and therefore it looks like this (notice [ ] brackets):
{"person":[{"PersonID":1,"Name":"John Smith","Gender":0}]}
So, the redux store shows it as person->0->{"PersonID":1,"Name":"John Smith","Gender":0}. As such, the state shows that the person object is empty:
Name: this.props.person?this.props.person.Name:'object is empty',
My PersonPage.js includes the details page like this:
<PersonDetail person={this.props.person} />
The details page has this:
import React from 'react';
import classnames from 'classnames';
class PersonDetail extends React.Component {
state = {
Name: this.props.person?this.props.person.Name:'',
PersonID: this.props.person?this.props.person.PersonID:null,
loading: false,
done: false
}
componentWillReceiveProps = (nextProps) => {
this.setState({
PersonID: nextProps.person.PersonID,
Name: nextProps.person.Name
});
}
This is my raw Redux state:
people: [
[
{
PersonID: 51,
Name: 'John Smith',
Gender: 0
}
]
]
Person is an array, that contains the object in which Name key is present, so you need to use index also, write it like this:
this.props.person && this.props.person.length ? this.props.person[0].Name : '';
Check this example:
var data = {
"person":[
{
"PersonID":1,
"Name":"John Smith",
"Gender":0
}
]
};
console.log('Name: ', data.person[0].Name);
I think that you are supposed to map the person detail foreach person's data.
on the PersonPage.js ,
map it as follows:
{
this.props.person.map((p)=>{
return (<PersonDetail person={p} />)
})
}
If I was you I would make an util function like this:
const parsePeople = people => {
if (people instanceof Array) return parsePeople(people.pop())
return people
}
const people = [
[{
PersonID: 51,
Name: 'John Smith',
Gender: 0
}]
]
const person = parsePeople(people)
console.log(person) -> Object
Using recursion we check if people is an instance of Array, we call the function again using people.pop() which return the last element of the array.
you have an array on your person data... you can only access that without the 0 using map...
example:
componentWillReceiveProps = (nextProps) => {
var PersonID = nextProps.person ? nextProps.person.map(item => { return item.PersonID}) : '';
var Name = nextProps.person ? nextProps.person.map(item => { return item.Name}) : '';
this.setState({
PersonID,
Name
});
}
this is considering you only have 1 array on person.
I fixed it! It was a combination of two of the answers given:
In the PersonPage.js, I had to call the PersonDetails object like this:
<PersonDetail
person={this.props.person[0]}
/>
And this is the new MapStatetoProps:
function mapStateToProps(state, props) {
const { match } = props;
if (match.params.PersonID) {
return {
person: state.people
}
}
Thanks to those who answered. This drove me nuts.

Mongoose .populate() only showing 1 document

I am trying to output just the hometeam name's to the page so that I can try to understand how to work with my code better. It is only printing one team to the page, and it is printing all the details of that team to the page, whereas I only want it to print one part.
This is my code, I want it to print the name's of each hometeam to the page
app.get('/home', function(req, res) {
Match.findOne({}).populate('hometeam.name').exec(function(err, teams){
util.log(teams);
res.send(teams);
});
});
But when I load the page all I get is the first piece of data from this list of Matches
[
{
"hometeam": "5106e7ef9afe3a430e000007",
"_id": "5113b7ca71ec596125000005",
"__v": 0,
"key": 1360246730427
},
{
"hometeam": "5113c13e0eea687b28000001",
"_id": "5113e951354fe70330000001",
"__v": 0,
"key": 1360259409361
},
{
"hometeam": "5113c13e0eea687b28000001",
"_id": "5113e999354fe70330000002",
"__v": 0,
"key": 1360259481412
}
]
Also, if I try to put util.log(teams.hometeam.name) I get the following:
TypeError: Cannot call method 'toString' of undefined
But I would want it to be able to print the name which belongs to hometeam here. As hometeam is just the objectId of a Team in my database, am I missing something with the DBreferencing here?
Update:
Team Schema
var Team = new Schema({
'key' : {
unique : true,
type : Number,
default: getId
},
'name' : { type : String,
validate : [validatePresenceOf, 'Team name is required'],
index : { unique : true }
}
});
module.exports.Schema = Team;
module.exports.Model = mongoose.model('Team', Team);
Match Schema
var Team = require('../schemas/Team').Schema;
var Match = new Schema({
'key' : {
unique: true,
type: Number,
default: getId
},
'hometeam' : { type: Schema.ObjectId, ref: 'Team' },
'awayteam' : { type: Schema.ObjectId, ref: 'Team' }
});
module.exports = mongoose.model('Match', Match);
Populate takes the property name of the property you are trying to retrieve. This means that you should use 'hometeam' instead of 'hometeam.name'. However, you want to retrieve the name of the team so you could filter for that. The call would then become..
Match.findOne({}).populate('hometeam', {name: 1}).exec(function(err, teams)
Now you have a property called 'hometeam' with in that the name. Have fun :)
EDIT
Showing how to have a single mongoose instance in more files to have correct registration of schemas.
app.js
var mongoose = require('mongoose');
var Team = require('./schemas/team-schema')(mongoose);
var Match = require('./schemas/match-schema')(mongoose);
// You can only require them like this ONCE, afterwards FETCH them.
var Team = mongoose.model('Team'); // LIKE THIS
schemas/match-schema.js
module.exports = function(mongoose) {
var Match = new mongoose.Schema({
'key' : {
unique: true,
type: Number,
default: getId
},
'hometeam' : { type: mongoose.Schema.ObjectId, ref: 'Team' },
'awayteam' : { type: mongoose.Schema.ObjectId, ref: 'Team' }
});
return mongoose.model('Match', Match);
};
schemas/team-schema.js
module.exports = function(mongoose) {
var Team = new mongoose.Schema({
'key' : {
unique : true,
type : Number,
default: getId
},
'name' : { type : String,
validate : [validatePresenceOf, 'Team name is required'],
index : { unique : true }
}
});
return mongoose.model('Team', Team);
};