Populate model of the model with sails js - json

I'm trying to populate model of the model with sails unfortunally it doesn't work.
I have 3 models
/**
Conversation.js
**/
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'conversation',
attributes: {
idConversation:{
columnName:'IDCONVERSATION',
primaryKey:true,
autoIncrement:true,
unique:true,
type:'integer',
index:true
},
dateStartConversation:{
columnName:'DATEDEBUT',
type:'date',
index:true
},
user1:{
columnName:'IDUSER1',
model:'user',
notNull:true
},
user2:{
columnName:'IDUSER2',
model:'user',
notNull:true
},
article:
{
model:'article',
columnName:'IDARTICLE',
notNull:true
}
}
};
/**
Article.js
**/
module.exports = {
autoPK: false,
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'article',
attributes: {
idArticle:{
type:'integer',
unique:true,
columnName:'IDARTICLE',
autoIncrement:true,
primaryKey:true
},
title:{
type:'string',
required:true,
columnName:'TITRE',
index:true,
notNull:true
},
utilisateur:{
model:'utilisateur',
columnName:'IDUTILISATEUR',
required:true,
notNull:true,
dominant:true
},
images:{
collection:'image',
via:'article'
},
conversation:{
collection:'conversation',
via:'article'
}
}
};
/**
Image.js
**/
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'image',
attributes: {
idImage:{
columnName:'IDIMAGE',
primaryKey:true,
autoIncrement:true,
unique:true,
type:'integer'
},
pathImage:{
columnName:'PATHIMAGE',
required:true,
type:'string',
notNull:true
},
article:{
model:'article',
columnName:'IDARTICLE',
notNull:true,
dominant:true
}
}
};
As you can see in my model, an conversation its between Two user, about one article, and those article cas have one or many Images.
So I want to get all conversations of one user and I able to populate with article but I'm not able to populate article with Image below how I proceed
Conversation.find().populate('article').populate('user1').populate('user2').where({
or : [
{ user1: iduser },
{ user2: iduser }
]})
.then(function( conversations) {
var i=0;
conversations.forEach(function(element,index){
i++;
console.log("article "+index+" "+JSON.stringify(element.article));
Article.findOne({
idArticle:element.article.idArticle
}).populate('images').then(function(newArticle){
//I try to set article with the newArticle but it don't work
element.article=newArticle;
})
if(i==conversations.length){
res.json({
hasConversation:true,
conversation:conversations
});
}
});
})
Because deep populate is not possible using sails, I try to use a loop to populate each article with associate Images and set it in conversation, But article is never set in conversation.
How can I fix it ?

Judging by the if(i==conversations.length) at the end, you seem to have an inkling that you need to write asynchronous code. But you're iterating i inside of the synchronous forEach loop, so your response is happening before any of the database queries even run. Move the i++ and the if inside of the callback for Article.findOne:
Conversation.find().populate('article').populate('user1').populate('user2').where({
or : [
{ user1: iduser },
{ user2: iduser }
]})
.then(function( conversations) {
var i=0;
conversations.forEach(function(element,index){
console.log("article "+index+" "+JSON.stringify(element.article));
Article.findOne({
idArticle:element.article.idArticle
}).populate('images').then(function(newArticle){
// Associate the article with the conversation,
// calling `toObject` on it first
element.article= newArticle.toObject();
// Done processing this conversation
i++;
// If we're done processing ALL of the conversations, send the response
if(i==conversations.length){
res.json({
hasConversation:true,
conversation:conversations
});
}
})
});
})
You'll also need to call toObject on the newArticle instance before assigning it to the conversation, because it contains getters and setters on the images property which behave unexpectedly when copied.
I'd also recommend refactoring this to use async.each, which will make it more readable.

Until this is resolved (https://github.com/balderdashy/sails-mongo/issues/108), you can use this function that I developed to solve this: https://gist.github.com/dinana/52453ecb00d469bb7f12

Related

how can I update the number of columns in angular-datatables with server-side rendering. version 6.0.0 or higher

I am having trouble getting my angular-datatable to show a new column list after a rerender. I have followed the example shown in the docs for rerendering and I can get the table to rerender. I am able to manipulate certain features like searching and pageLength, but for some reason I cannot get my columns to change.
I have a very deep data set that would make my table look awful if I rendered all the columns at once, so I would like to give users the ability to select which columns they see.
I would even be open to loading in all the columns at once and just switching visibility off and on, but I cannot effect visibility either.
Has anyone had this issue before?
Rerender function:
rerender(): void {
this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
dtInstance.destroy();
// these work
this.dtOptions.searching = true;
this.dtOptions.pageLength = 2;
// these do not
this.dtOptions.columns = newColumnList;
this.dtOptions.columns[some-index].visible = false;
this.dtTrigger.next();
});}
Initial dtOptions:
this.dtOptions = {
searching: false,
pagingType: 'full_numbers',
pageLength: 10,
retrieve: true,
serverSide: true,
processing: true,
language: {
zeroRecords: 'Nothing Found'
},
ajax: (dataTablesParameters: any, callback) => {
const payload = this.passFilterService.processPagination(this.filter, dataTablesParameters);
this.http
.post<any>(
environment.api + '/things/list',
{payload: payload}, {}
).subscribe(resp => {
if (resp.data.data === null) {
resp.data.data = 0;
}
callback({
recordsFiltered: resp.data.totalCount,
data: resp.data.data,
recordsTotal: resp.data.totalCount
});
});
},
columns: this.tableColumns
};
Initial Columns (limited fields):
tableColumns = [
{
title: 'Customer',
data: 'Id',
render: function(data) {
return `Action`;
}
}, {
title: 'Created',
data: 'createdAt',
orderable: true,
visible: true,
}, {
title: 'Updated',
data: 'updatedAt',
orderable: true,
visible: true,
}, {
title: 'Disabled',
data: 'isVoided',
orderable: true,
visible: true,
}
];
Table implementation:
<table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="row-border hover">
</table>
I faced the same issue, spent hours debugging it until I found something that worked for me. I will advice separating the DT config into an independent object that can be loaded separately. Once you update your DT options and any other config, you can use the functions below to reload the entire DT, destroying and reloading it accordingly;
async rerender(newSettings?: DataTables.Settings) {
try {
this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
if (newSettings) {
// FIX To ensure that the DT doesn't break when we don't get columns
if (newSettings.columns && newSettings.columns.length > 1) {
dtInstance.destroy();
this.dtOptions = Promise.resolve(newSettings);
this.displayTable(this.dtTableElement);
}
}
});
} catch (error) {
console.log(`DT Rerender Exception: ${error}`);
}
return Promise.resolve(null);
}
This function calls the below one to actually destroy the DT and rerender it.
private displayTable(renderIn: ElementRef): void {
this.dtElement.dtInstance = new Promise((resolve, reject) => {
Promise.resolve(this.dtOptions).then(dtOptions => {
// Using setTimeout as a "hack" to be "part" of NgZone
setTimeout(() => {
$(renderIn.nativeElement).empty();
var dt = $(renderIn.nativeElement).DataTable(dtOptions);
// this.dtTrigger.next();
resolve(dt);
});
}).catch(error => reject(error));
});
}
I removed the dtTrigger execution from the reconstruction function as this was executing twice.
The dtTableElement is defined as #ViewChild('dtTableElement') dtTableElement: ElementRef; where the HTML contains the respective reference on the datatable as:
<table #dtTableElement datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="table table-striped row-border hover" width="100%"></table>

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);
});

Query data relationship on Firebase Realtime Database with angularfire2

I need to query comments and request only user that listed in the comment by userId.
My database structure in Firebase realtime db:
{
"comments" : {
"c_id1" : {
"commentId" : "c_id1",
"commentText" : "text",
"userId" : "u_id1"
},
"c_id2" : {
"commentId" : "c_id2",
"commentText" : "text",
"userId" : "u_id3"
},
},
"users" : {
"u_id1" : {
"userId" : "u_id1",
"userName" : "name1",
},
"u_id1" : {
"userId" : "u_id2",
"userName" : "name2",
},
"u_id1" : {
"userId" : "u_id3",
"userName" : "name3",
}
}
}
What I need in the end is Comment[], where Comment is:
{
"commentId" : "c_id",
"commentText" :"text",
"userId" : "u_id",
"user" : {
"userId":"u_id",
"userName":"name"
}
}
so, the class for Comment is
export class Comment {
commentId: string;
commentText: string;
userId: string;
user?: User;
}
So far I managed to get ALL users and then map them to comments on the client side. But wouldn't it be to much in case when db will have N number of users and only 2 comments, where N>>2?
OnGetUsersForComments(){
return this.angularFireDatabase.list("/comments").valueChanges()
.subscribe((data) => {
this.commentsUsers = data;
this.OnGetCommentsForTask()
});
}
OnGetCommentsForTask(){
this.angularFireDatabase.list("/comments").valueChanges()
.map((comments) => {
return comments.map( (comment: TaskComment) => {
this.commentsUsers.forEach((user: User) => {
if (comment.userId === user.userId) {
comment.commentUser = user;
}
});
return comment;
});
})
.subscribe((data)=> {
this.comments = data;
});
}
Is there a way get only users from comments?
I also tried to add this to the User, but did not manage it to work:
"userComments" : {
"uc_id1" : {
"commentId" : c_id2
},
}
Update 0
I have edited the question, I hope now is more clear.
I have been able to make it work like this:
solution from - https://www.firebase.com/docs/web/guide/structuring-data.html
and
https://firebase.google.com/docs/database/web/read-and-write
comments: TaskComment[] = [];
onGetComments(){
var ref = firebase.database().ref('/');
ref.child('comments/').on('child_added', (snapshot)=>{
let userId = snapshot.val().userId;
ref.child('users/' + userId).on('value', (user)=>{
this.comments.push( new TaskComment( snapshot.val(), user.val() ));
});
});
}
but I want to convert this to Observable, because with this I can not see if the comment have been deleted without refreshing the page.
Update 1
With the help from comment bellow I came out with this implementation.
onGetComments(){
this.angularFireDatabase.list("/comments").valueChanges()
.mergeMap((comments) => {
return comments.map((comment)=>{
this.firebaseService
.onListData('/users', ref => ref.orderByChild('userId').equalTo(comment.userId))
.valueChanges()
.subscribe((user: User[])=> {
comment.user = user[0];
})
return comment;
})
})
.subscribe((comment)=> {
console.log(comment);
});
}
This returns separate comments, where I would rather receive Comment[], I'll try to use child events: "child_added", "child_changed", "child_removed", and "child_moved" with snapshotChanges() instead .valueChanges().
Ok so according to your updates, I would personally first create a couple helper interfaces:
interface User {
userId: string;
userName: string;
}
interface FullComment {
commentId: string;
userId: string;
user: User;
}
interface CommentObject {
commentId: string;
commentText: string;
userId: string;
}
And then super handy helper methods:
getUser(uid: string): Observable<User> {
return this.db.object<User>(`/users/${uid}`)
.valueChanges()
}
getFullComment(commentObject: CommentObject): Observable<FullComment> {
return this.getUser(commentObject.userId)
.map((user: User) => {
return {
commentId: commentObject.commentId,
commentText: commentObject.commentText,
user: user,
};
});
}
So finally look how easy it becomes to get the FullComment objects observable:
getComments(): Observable<FullComment[]> {
return this.db
.list(`/comments`)
.valueChanges()
.switchMap((commentObjects: CommentObject[]) => {
// The combineLatest will convert it into one Observable
// that emits an array like: [ [fullComment1], [fullComment2] ]
return Observable.combineLatest(commentObjects.map(this.getFullComment));
});
}
I think this is what you need. Please let me know if this is helpful.
Happy coding with observables ;)
Latest update: Previously forgot to make a last transformation to fix the TypeError, so now it must be ok.

Ember Data: Saving relationships

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/

Sencha Touch searchable list -- new view with data proxy

Two quick questions here... How can I use this example
http://try.sencha.com/touch/2.0.0/examples/list-search/
of a searchable list, but opened in a NEW view? The example has it defined as the main application in app.js, but I would like to use it in "FirstApp.view.searchlist"
I know the answer is pretty easy but I am still a young grasshoppa and need a push in the right direction.
Also, rather than pulling the data from the embedded store like the example, I would like to modify it to pull my data from my external/proxy JSON store, which is defined as follows:
Store:
Ext.define('FirstApp.store.StudentStore',{
extend:'Ext.data.Store',
config:{
autoLoad:true,
model:'FirstApp.model.people',
sorters: 'lastName',
proxy:{
type:'ajax',
url:'http://xxxyyyzzz.com/data/dummy_data.json',
reader:{
type:'json',
rootProperty:'results'
}
}
}
});
Model:
Ext.define('FirstApp.model.people', {
extend: 'Ext.data.Model',
config: {
fields: ['firstName', 'lastName' , 'image','status', 'phone','rank','attendance', 'discipline','recent']
}
});
So, how can I turn that example into a "view" inside my application, with my data store and model?
Any help is greatly appreciated! Thank you!
Jake
-----------UPDATE-------------
Ok fantastic. I was able to implement the search feature (stoked) by combining your methods with another tutorial I found. Now one more question...Seems so easy but it is tough! How can I open my new 'Details' view once an item is selected/clicked ??
Search list:
Ext.define('FirstApp.view.MainPanel', {
extend: 'Ext.dataview.List',
alias : 'widget.mainPanel',
config: {
store : 'Students',
itemTpl:
'<h1>{firstName:ellipsis(45} {lastName:ellipsis(45)}</h1>' ,
itemCls:'place-entry',
items: [
{
xtype: 'toolbar',
docked: 'top',
items: [
{
xtype: 'searchfield',
placeHolder: 'Search People...',
itemId: 'searchBox'
}
]
}
]
}
});
Details view (that I want to open when name is clicked from Search list/Mainpanel view):
Ext.define('FirstApp.view.Details',{
extend:'Ext.Panel',
xtype:'details',
config:{
layout:'fit',
tpl:'<div class="image_container"><img src="{image}"></div>' +
'<h1>{firstName:ellipsis(25)} {lastName:ellipsis(25)}</h1>'+
'<div class="status_container">{status:ellipsis(25)}</div> '+
'<div class="glance_container"> <div class="value_box"><div class="value_number"> {rank:ellipsis(25)}</div> <p class="box_name">Rank</p> </div> <div class="value_box"><div class="value_number"> {attendance:ellipsis(25)}</div> <p class="box_name" style="margin-left: -10px;">Attendance</p> </div> <div class="value_box"><div class="value_number">{discipline:ellipsis(25)}</div> <p class="box_name" style="margin-left: -4px;">Discipline</p> </div> <div class="value_box"><div class="value_number"> {recent:ellipsis(25)}</div> <p class="box_name">Recent</p> </div> </div> '+
'<h2>Phone:</h2> <div class="phone_num"><p>{phone:ellipsis(25)}</p></div>'+
'<h3>Some info:</h3><p>Round all corners by a specific amount, defaults to value of $default-border-radius. When two values are passed, the first is the horizontal radius and the second is the vertical radius.</p>',
scrollable:true,
styleHtmlContent:true,
styleHtmlCls:'details'
}
})
Search Controller:
Ext.define('FirstApp.controller.SearchController', {
extend : 'Ext.app.Controller',
config: {
profile: Ext.os.deviceType.toLowerCase(),
stores : ['StudentStore'],
models : ['people'],
refs: {
myContainer: 'MainPanel',
placesContainer:'placesContainer'
},
control: {
'mainPanel': {
activate: 'onActivate'
},
'mainPanel searchfield[itemId=searchBox]' : {
clearicontap : 'onClearSearch',
keyup: 'onSearchKeyUp'
},
'placesContainer places list':{
itemtap:'onItemTap'
}
}
},
onActivate: function() {
console.log('Main container is active');
},
onSearchKeyUp: function(searchField) {
queryString = searchField.getValue();
console.log(this,'Please search by: ' + queryString);
var store = Ext.getStore('Students');
store.clearFilter();
if(queryString){
var thisRegEx = new RegExp(queryString, "i");
store.filterBy(function(record) {
if (thisRegEx.test(record.get('firstName')) ||
thisRegEx.test(record.get('lastName'))) {
return true;
};
return false;
});
}
},
onClearSearch: function() {
console.log('Clear icon is tapped');
var store = Ext.getStore('Students');
store.clearFilter();
},
init: function() {
console.log('Controller initialized');
},
onItemTap:function(list,index,target,record){ // <-----NOT WORKING
this.getPlacesContainer().push({
xtype:'details',
store:'Students',
title:record.data.name,
data:record.data
})
}
});
Good question. I assume you are trying to build a List or dataview. The key here is to give your store a 'storeId'. I have modified your store below:
Ext.define('FirstApp.store.StudentStore',{
extend:'Ext.data.Store',
config:{
storeId: 'Students', // Important for view binding and global ref
autoLoad:true,
model:'FirstApp.model.people',
sorters: 'lastName',
proxy:{
type:'ajax',
url:'http://xxxyyyzzz.com/data/dummy_data.json',
reader:{
type:'json',
rootProperty:'results'
}
}
}
});
Then inside your view, you reference the store to bind to. Here is an example List view from one of my applications. Notice the config object has 'store' which references our above store:
Ext.define('app.view.careplan.CarePlanTasks', {
extend: 'Ext.dataview.List',
xtype: 'careplanTasks',
requires: [
'app.view.template.CarePlan'
],
config: {
store: 'Students', // Important! Binds this view to your store
emptyText: 'No tasks to display',
itemTpl: Ext.create('app.view.template.CarePlan'),
},
constructor : function(config) {
console.log('CarePlan List');
this.callParent([config]);
}
});
Now that you have a storeId, you can access this store anywhere in your application by doing the following:
Ext.getStore('Students')
You can load records from your server by calling the load method as well:
Ext.getStore('Students').load();
You can do this anywhere in your application, but typically it's best to do in your controllers.
Hope this helps.
======= Updates to your updates ======
So looking at your code I think you need to modify your List view and the controller. Give 'FirstApp.view.MainPanel' an xtype: 'MainPanel'. Next modify your controller config as follows:
config: {
profile: Ext.os.deviceType.toLowerCase(),
stores : ['StudentStore'],
models : ['people'],
refs: {
mainPanel: 'MainPanel', // set the object KEY to the name you want to use in the control object and set the VALUE to the xtype of the view
placesContainer:'placesContainer'
},
control: {
'mainPanel': { // this matches the key above, which points to your view's xtype
activate: 'onActivate',
itemtap: 'onItemTap' // listen for the item tap event on this List
},
'mainPanel searchfield[itemId=searchBox]' : {
clearicontap : 'onClearSearch',
keyup: 'onSearchKeyUp'
},
'placesContainer places list':{
itemtap:'onItemTap'
}
}
},