create a PhoneCall record in Crm using Javascript - javascript

When I try to create a PhoneCall record in Crm using Javascript, I got following error message, I cannot figure out the reason, any help?
{
"readyState": 4,
"responseText": "{\r\n\"error\": {\r\n\"code\": \"\", \"message\": {\r\n\"lang\": \"en-US\", \"value\": \"Error processing request stream. The property name 'from' specified for type 'Microsoft.Crm.Sdk.Data.Services.PhoneCall' is not valid.\"\r\n}\r\n}\r\n}",
"status": 400,
"statusText": "Bad Request"
}
"error"
"Bad Request"
<code>
var fromArray = [];
var FromActivityParty = {
PartyId:
{
Id: Xrm.Page.context.getUserId(),
LogicalName: "systemuser"
},
ActivityId: {
Id: TeamId,
LogicalName: "team"
},
ParticipationTypeMask: { Value: 1 }
};
fromArray[0] = FromActivityParty;
var toArray = [];
var ToActivityParty = {
PartyId:
{
Id: Xrm.Page.data.entity.getId(),
LogicalName: "account"
},
ActivityId: {
Id: TeamId,
LogicalName: "team"
},
ParticipationTypeMask: { Value: 2 }
};
toArray[0] = ToActivityParty;
var PhoneCall = {
from: fromArray,
to: toArray,
Subject: "Create a phonecall record",
OwnerId: fromArray,
PhoneNumber: phoneNumber
}
CrmRestKit.Create("PhoneCall", PhoneCall)
.fail(function (xhr, status, errorThrown)
{
console.log(JSON.stringify(xhr, null, 4));
console.log(JSON.stringify(status, null, 4));
console.log(JSON.stringify(errorThrown, null, 4));
})
.done(function (data, status, xhr) {
console.log(JSON.stringify(data, null, 4));
}
</code>

If you're using the REST endpoint, you need to add the To and From using the relationship with the activity party entity.
Here is the code I use:
var phoneCall = {};
phoneCall.phonecall_activity_parties = [
new ActivityParty(2, "systemuser", "GUID"),
new ActivityParty(1, "contact", "GUID")
]; //2 = 'To' 1 = 'From'
//TODO: Call CREATE using phoneCall object.
function ActivityParty(typeMask, logicalName, partyId) {
if (partyId && partyId[0] === '{') {
partyId = partyId.substring(1, partyId.length - 1);
}
this.PartyId = {
LogicalName : logicalName,
Id : partyId
};
this.ParticipationTypeMask = {
Value : typeMask
};
}

Well the error message sounds pretty clear to me.
"from" seems not to be a valid property name for the phone call type.
A quick guess: Have you tried to use "From" with an upper case? I see that your other properties are written this way.

Related

WKWebView evaluateJavaScript returns wrong JavaScript Object

I'm making a hybrid app and using WKWebView.
I need to pass a JavaScript Object to the emitter command to open the edit dialog.
Here is my code:
let statDict: [String: Any] = [
"income" : account.stat.income,
"expense" : account.stat.expense,
"summary" : account.stat.summary,
"incomeShorten" : account.stat.incomeShorten,
"expenseShorten" : account.stat.expenseShorten,
"summaryShorten": account.stat.summaryShorten
]
let accountDict: [String: Any] = [
"id": account.id,
"name": account.name,
"description": "",
"icon": account.icon,
"currency": account.currency,
"customer_contact_id": account.customer_contact_id ?? 0,
"is_archived": account.is_archived,
"sort": account.sort,
"create_datetime": account.create_datetime,
"update_datetime": account.update_datetime ?? "",
"stat": statDict
]
let accountData = try! JSONSerialization.data(withJSONObject: accountDict, options: JSONSerialization.WritingOptions(rawValue: 0))
guard let accountString = String(data: accountData, encoding: .utf8) else {
return
}
webView.evaluateJavaScript("function parse(string){ return JSON.parse(string)}") { result, error in
if error == nil { // this is returns correct staff
}
}
webView.evaluateJavaScript("parse('\(accountString)')") { object, error in
if error == nil {
let object = object as AnyObject
print("parse object \(object)")
webView.evaluateJavaScript("window.emitter.emit('openDialog', 'Account', \(object))") { (result, error) in
if error == nil { // here the error "Unexpected token '='..."
webView.evaluateJavaScript("window.emitter.on('closeDialog', function(){ window.webkit.messageHandlers.emitterMessage.postMessage('closeDialog'); })") { (result, error) in
if error == nil {
}
}
webView.evaluateJavaScript("window.emitter.on('createAccount', function(){ window.webkit.messageHandlers.emitterMessage.postMessage('createAccount'); })") { (result, error) in
if error == nil {
}
}
} else {
print(error as Any)
}
}
}
}
The \ (object) returned by the function looks like this:
{
"create_datetime" = "2021-08-24 19:19:28";
currency = RUB;
"customer_contact_id" = 1;
description = "";
icon = "";
id = 7;
"is_archived" = 0;
name = "Business 111";
sort = 0;
stat = {
expense = 0;
expenseShorten = 0;
income = 300000;
incomeShorten = 300K;
summary = 300000;
summaryShorten = 300K;
};
"update_datetime" = "";
}
but it should look like this:
{
create_datetime: "2021-08-24 19:19:28",
currency: "RUB",
customer_contact_id: 1,
description: "",
icon: "",
id: 7,
is_archived: false,
name: "Business 111",
sort: 0,
stat: {
expense: 0,
expenseShorten: "0",
income: 300000,
incomeShorten: "300K",
summary: 300000,
summaryShorten: "300K"
},
update_datetime: ""
}
With such an object, the compiler generates the error Unexpected token '='. Expected an identifier as property name.
The parse (string) function will return the correct object if you run it in the js compiler, but in swift the output is not correct.
How to bring an object to the correct form?
You are trying to pass the string interpolated representation of a Swift object (NSMutableDictionary in your case) to Javascript.
Instead you can directly pass the JSON representation to JS context since JSON is a native Javascript object it should do what you are trying to achieve :
/// Sample emitter function that consumes object an prints its local parameter, also assigns it to sample object value in window.
self.webView?.evaluateJavaScript(
"window.emitter = (sampleObject) => { window.sampleObject = sampleObject;setTimeout(function(){console.log('Hello sampleObject : ',sampleObject.name); }, 7000);}"
) { result, error in
if error == nil { // this is returns correct staff
}
}
self.webView?.evaluateJavaScript("window.emitter(\(accountString));") { result, error in
if error == nil {
print("parse object \(result)")
}
}
Result in window :

JSON response from Watson Translator Undefined

I am trying to use Watson translator service from my node.js app through the API documentation from IBM https://www.ibm.com/watson/developercloud/alchemyvision/api/v1/#apiexplorer
var request = require("request");
var LanguageTranslatorV3 = require('watson-developer-cloud/language-translator/v3');
var english_message
var languageTranslator = new LanguageTranslatorV3({
version: '2018-05-01',
username: '1234',
password: '1234',
url: 'https://gateway.watsonplatform.net/language-translator/api'
});
function translatorEnglish(message) {
var parameters = {
text: message.text,
model_id: 'es-en'
};
languageTranslator.translate(
parameters,
function(error, response, body) {
if (error)
console.log(error)
else
console.log(JSON.stringify(response, null, 2));
}
);
}
I get the following correct response in logs
{
"translations": [
{
"translation": "Hi."
}
],
"word_count": 1,
"character_count": 4
}
but when i try to extract the output translation value i always get the output as Undefined.
console.log(response.translations.translation); => undefined
Can you please check and let me know if i am doing anything wrong?
Thanks
Try
console.log(response.translations[0].translation); // Hi
This is because translations is an array and has one item at index 0.
{
"translations": [
{
"translation": "Hi."
}
],
"word_count": 1,
"character_count": 4
}

Rocket Chat Realtime API in browser

I want to create a Rocket Chat client to subscribe to a channel through browser using Realtime API. Documentation here does not provide step by step procedure. Please let me know how to achieve it.
Links to any documentation would be very helpful.
I had less idea about websockets when I asked this question. For the benefit of all, mentioning the steps I followed.
Open websocket
var rocketChatSocket = new WebSocket("ws://locahost:3000/websocket");
Connect
var connectRequest = {
"msg": "connect",
"version": "1",
"support": ["1", "pre2", "pre1"]
}
rocketChatSocket.send(JSON.stringify(connectRequest));
After connecting, keep responding with {"msg":"pong"} for {"msg":"ping"} from server.
Login with authToken received by calling this API
var loginRequest = {
"msg": "method",
"method": "login",
"id": "42",
"params": [
{ "resume": "authToken" }
]
}
rocketChatSocket.send(JSON.stringify(loginRequest));
Subscribe to room
var subscribeRequest = {
"msg": "sub",
"id": "unique-id",
"name": "stream-notify-room",
"params":[
"room-id/event",
false
]
}
rocketChatSocket.send(JSON.stringify(subscribeRequest));
Send message
var request={
"msg": "method",
"method": "sendMessage",
"id": "42",
"params":
[
{
"_id": "message-id",
"rid": "room-id",
"msg": "Hello World!"
}
]
}
rocketChatSocket.send(JSON.stringify(request));
Thank you Mr Nandan,we were able to use your answer to build fully custom client to talk with the rocket chat real time api
here's the link for future people who will want the same thing
https://github.com/lalosh/rocketchat.realTimeAPI.customClient/blob/master/main.js
and here's the code in case you want to check immediately
/*
Rocket Chat Real Time API Custom Client
even though this code works great I don't know what exactly each event we listen for is doing
you can go back to rocket chat real time api for further declarations
we were able to write this code after we test real normal use case of livechat in a web page
and we listen for WebSocket connection inside the browser Network tab(by filtering ws(WebSocket) connections)
*/
let socket = new WebSocket('ws://[rocketChatIP]:[rocketChatPort]/websocket');
//note messageCount is incremented with every message
//but it can works even if you didn't change it
let messagesCount = 1;
// the variables chatToken and chatRoomId are very important
// and they are the identifier to the room(the whole chat) you are using
// if you want to get access to the chat again you need these two variables tokens
let chatToken = generateHash(17);
let chatRoomId = generateHash(17);
let subId = generateHash(17);
let roomSubId = generateHash(17);
let streamLivechatchatRoomId = generateHash(17);
let steamNotifyRoomSubId = generateHash(17);
let name = 'user28';
let email = 'user28#gmail.com';
let guestName = 'guest';
// listen to messages passed to this socket
socket.onmessage = function(e) {
let response = JSON.parse(e.data);
console.log('response', response);
// you have to pong back if you need to keep the connection alive
// each ping from server need a 'pong' back
if (response.msg == 'ping') {
console.log('pong!');
socket.send(JSON.stringify({
msg: 'pong'
}));
return;
}
// here you receive messages from server //notive the event name is: 'stream-room-messages'
if (response.msg === 'changed' && response.collection === 'stream-room-messages') {
console.log('msg received ', response.fields.args[0].msg, 'timestamp ', response.fields.args[0].ts.$date, 'from ' + response.fields.args[0].u.name);
return;
}
// receive all messages which will only succeed if you already have messages
// in the room (or in case you send the first message immediately you can listen for history correctly)
if (response.msg === 'result' && response.result) {
if (response.result.messages) {
let allMsgs = response.result.messages;
console.log('-----previous msgs---------------');
allMsgs.map(x => console.log(x))
console.log('---------------------------------')
}
}
}
//////////////////////////////////////////////
// steps to achieve the connection to the rocket chat real time api through WebSocket
//1 connect
let connectObject = {
msg: 'connect',
version: '1',
support: ['1', 'pre2', 'pre1']
}
setTimeout(() => {
socket.send(JSON.stringify(connectObject));
}, 1000)
//////////////////////////////////////////////
//2 getInitialData
let getInitialDataObject = {
msg: 'method',
method: 'livechat:getInitialData',
params: [String(chatToken), null],
id: String(messagesCount++),
}
setTimeout(() => {
socket.send(JSON.stringify(getInitialDataObject));
}, 2000)
//////////////////////////////////////////////
//3 loginByToken
let loginByToken = {
msg: 'method',
method: 'livechat:loginByToken',
params: [String(chatToken)],
id: String(messagesCount++),
}
setTimeout(() => {
socket.send(JSON.stringify(loginByToken));
}, 3000)
//////////////////////////////////////////////
//4 subscribtion
let subObj = {
msg: 'sub',
id: subId,
name: 'meteor.loginServiceConfiguration',
params: []
}
setTimeout(() => {
socket.send(JSON.stringify(subObj));
}, 4000)
//////////////////////////////////////////////
//5 register // you may skip this if you alredy registered
// or you can re use it even though you are registered.. no problems
// the crucial part is the `chatToken` and later on the `chatRoomId`
let registerObj = {
msg: 'method',
method: 'livechat:registerGuest',
params: [{
token: chatToken,
name: name,
email: email,
'department': null
}],
id: String(messagesCount++),
};
setTimeout(() => {
socket.send(JSON.stringify(registerObj));
}, 5000)
//////////////////////////////////////////////////
//6 stream-notify-room
let streamNotifyObj = {
msg: 'method',
method: 'stream-notify-room',
params: [
'null/typing',
guestName, true, {
token: String(chatToken)
}
],
id: String(messagesCount++)
};
setTimeout(() => {
socket.send(JSON.stringify(streamNotifyObj));
}, 6000)
//////////////////////////////////////////////////
//7 send a msg //use the same method to send your own messages again when you are all connected
let myMsg = {
msg: 'method',
method: 'sendMessageLivechat',
params: [{
_id: String(generateHash(17)),
rid: chatRoomId,
msg: 'first message',
token: String(chatToken),
}, null],
id: String(messagesCount++),
}
setTimeout(() => {
socket.send(JSON.stringify(myMsg));
}, 7000)
//////////////////////////////////////////////////
//8 send userPresence
let UserPresence = {
msg: 'method',
method: 'UserPresence:connect',
params: [
String(chatToken),
{
visitor: String(chatToken)
}
],
id: String(messagesCount++)
}
setTimeout(() => {
socket.send(JSON.stringify(UserPresence));
}, 8000)
/////////////////////////////////////////////////
//9 loadHistory of old messages
let loadHistory = {
msg: 'method',
method: 'livechat:loadHistory',
params: [{
token: String(chatToken),
rid: String(chatRoomId),
ts: {
$date: new Date().getTime()
}, //current point of time
limit: 50
}],
id: String(messagesCount++)
};
setTimeout(() => {
socket.send(JSON.stringify(loadHistory));
}, 9000)
/////////////////////////////////////////////////
// 10 stream-room-messages
// listen to all received messages
let roomMessagesSub = {
msg: 'sub',
id: String(roomSubId),
name: 'stream-room-messages',
params: [
String(chatRoomId),
{
useCollection: false,
args: [{
visitorToken: String(chatToken)
}]
}
]
};
setTimeout(() => {
socket.send(JSON.stringify(roomMessagesSub));
}, 10000)
/////////////////////////////////////////////////
// 11 getAgentData
let getAgentData = {
msg: 'method',
method: 'livechat:getAgentData',
params: [{
roomId: String(chatRoomId),
token: String(chatToken),
}],
id: String(messagesCount++)
}
setTimeout(() => {
socket.send(JSON.stringify(getAgentData));
}, 11000)
/////////////////////////////////////////////////
//12 stream-livechat-room
let streamLivechatRoom = {
msg: 'sub',
id: String(streamLivechatchatRoomId),
name: 'stream-livechat-room',
params: [
String(chatRoomId),
{
useCollection: false,
args: [{
'visitorToken': String(chatToken)
}]
}
]
}
setTimeout(() => {
socket.send(JSON.stringify(streamLivechatRoom));
}, 12000)
//////////////////////////////////////////
//13 stream-notify-room
let steamNotifyRoomSub = {
msg: 'sub',
id: String(steamNotifyRoomSubId),
name: 'stream-notify-room',
params: [
`${String(chatRoomId)}/typing`,
{
useCollection: false,
args: [{
token: String(chatToken)
}]
}
]
}
setTimeout(() => {
socket.send(JSON.stringify(steamNotifyRoomSub));
}, 13000)
//////////////////////////////////////
//14 userpresence 2
let UserPresence2 = {
msg: 'method',
method: 'UserPresence:online',
params: [String(chatToken)],
id: String(messagesCount++),
}
setTimeout(() => {
socket.send(JSON.stringify(UserPresence2));
}, 14000)
//////////////////////////////////////
//use it to send new messages
function sendNewMsg(msg, messagesCount) {
let myMsg = {
msg: 'method',
method: 'sendMessageLivechat',
params: [{
_id: String(generateHash(17)),
rid: chatRoomId,
msg: String(msg),
token: String(chatToken),
}, null],
id: String(messagesCount++),
}
setTimeout(() => {
socket.send(JSON.stringify(myMsg));
}, 500);
}
function generateHash(targetLength) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < targetLength; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
If you like to use an open source library for this purpose, here is one https://github.com/inf3cti0n95/Rocket.Chat.RealTime.API.RxJS
Here is the sample code:
import { RealTimeAPI } from "rocket.chat.realtime.api.rxjs";
const realTimeAPI = new RealTimeAPI("wss://demo.rocket.chat/websocket");
realTimeAPI.keepAlive();
const auth = realTimeApi.login(USERNAME, PASSWORD);
//Subscribe to messages and errors
auth.subscribe(
(data) => console.log(data),
(err) => console.log(err),
() => console.log('completed'));
//Subscribe to a specific channel, identified by roomId
realtimeAPI.sendMessage({
msg: 'sub',
id: '<a unique Id for subscription>',
name: 'stream-room-messages',
params: [roomId, false],
});

Export table values Meteor Blaze

I am running into some difficulty exporting a table to csv in meteor/blaze. I am following: [http://rafaelquintanilha.com/export-your-json-data-to-csv-format/][1].
I have a Template.event that is triggering the export button
Template.export.onCreated( () => {
Template.instance().subscribe('table');
});
Template.export.helpers({
exportContacts() {
return Contacts.find();
}
});
Template.export.events({
'click .export-data' () {
MyAppExporter.exportAllContacts();
}
});
it is calling exportAllContacts() in a global helper
MyAppExporter = {
exportAllContacts: function() {
var self = this;
Meteor.call("exportContacts", function(error, data) {
if ( error ) {
alert(error);
return false;
}
var csv = Papa.unparse(data);
self._downloadCSV(csv);
});
},
_downloadCSV: function(csv) {
var blob = new Blob([csv]);
var a = window.document.createElement("a");
a.href = window.URL.createObjectURL(blob, {type: "text/plain"});
a.download = "contacts.csv";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}
and the helper is calling a Meteor.method exportContacts
Meteor.methods({
exportContacts: function() {
let fields = [
"Email",
“Some Contact",
"Created Date",
"Hard Bounce",
"Unsubscribed"
];
let data = [];
let contacts = Contacts.find().fetch();
for(let i = 0; i < contacts.length; i++) {
let contact = contacts[i];
let contactString = JSON.stringify(contact);
_.each(contactString, function(c) {
console.log("Inside Loop", contactString);
data.push([
c.contact.emailAddress,
c.contact.someContact,
c.contact.creationDate,
c.contact.hardBounceBack,
c.contact.unsubscribed
]);
console.log("DATA", data)
return {fields: fields, data: data};
});
}
}
});
I keep getting an error that “emailAddress is not defined exportContacts.js:20:17
20160426-22:00:47.957(-4)? Inside Loop {"_id":"dRnXRdZrbR9CYdmBx","contact":[{"emailAddress":"fred#weasly.com","someContact":"No","creationDate":"N/A","hardBounceBack":"N/A","unsubscribed":"N/A"}]}
I20160426-22:00:48.029(-4)? Exception while invoking method 'exportContacts' ReferenceError: emailAddress is not defined
I20160426-22:00:48.029(-4)? at server/methods/exportContacts.js:20:17
I20160426-22:00:48.029(-4)? at Function._.each._.forEach (packages/underscore.js:142:22)
I20160426-22:00:48.029(-4)? at _loop (server/methods/exportContacts.js:17:7)
but I cannot seem to figure out how to access the contacts. I am logging it out (see above in logs). Any help would be appreciated.
ADDED LOGS
let contacts = Contacts.find().fetch(); console.log(contacts)
I20160427-09:06:23.484(-4)? CONTACTS [ { _id: 'dRnXRdZrbR9CYdmBx', contact: [ [Object] ] },
I20160427-09:06:23.484(-4)? { _id: 'LHmW4R9PLM5D7cZxr', contact: [ [Object] ] },
I20160427-09:06:23.484(-4)? { _id: 'jBdqQXz2b8itXJowX', contact: [ [Object] ] },
I20160427-09:06:23.484(-4)? { _id: 'bnDvNGX3i879z4wr2', contact: [ [Object] ] } ]
c.contact[0].emailAddress logged out
I20160427-09:22:08.142(-4)? Inside Loop {"_id":"dRnXRdZrbR9CYdmBx","contact":[{"emailAddress":"fred#weasly.com","someContact":"No","creationDate":"N/A","hardBounceBack":"N/A","unsubscribed":"N/A"}]}
I20160427-09:22:08.217(-4)? Exception while invoking method 'exportContacts' TypeError: Cannot read property '0' of undefined
I20160427-09:22:08.217(-4)? at server/methods/exportContacts.js:21:7
I20160427-09:22:08.217(-4)? at Function._.each._.forEach (packages/underscore.js:142:22)
I20160427-09:22:08.217(-4)? at _loop (server/methods/exportContacts.js:18:7)
I20160427-09:22:08.218(-4)? at [object Object].exportContacts (server/methods/exportContacts.js:15:46)
Within the _.each loop you are accessing the wrong data items. You can also use a _.each loop instead of the outer for loop too. If you do :
let contacts = Contacts.find().fetch();
_.each(contacts, function(contact) {
_each(contact.contact, function(c) {
data.push(
{
"email": c.emailAddress,
"contact": c. someContact,
"creationDate" : c.creationDate,
"bounceBack": c.hardBounceBack,
"unsubscribed": c.unsubscribed
}
})
})
This should solve your problem. This way you are looping through the outer contacts that is coming back from the fetch and then looping through the contact array from each element. This should be the most direct way to get down to the data you are looking for.
Your problem is this line: _.each(contactString, function(c) {
It should read: _.each(contact, function(c) {
:)

ES6 Classes for Data Models

I'm trying to use ES6 Classes to construct data models (from a MySQL database) in an API that I'm building. I prefer not using an ORM/ODM library, as this will be a very basic, simple API. But, I'm struggling to get my head around how to define these models.
My data entities are (these are just some simplified examples):
CUSTOMER
Data Model
id
name
groupId
status (enum of: active, suspended, closed)
Private Methods
_getState(status) {
var state = (status == 'active' ? 'good' : 'bad');
return state;
}
Requests
I want to be able to do:
findById: Providing a single customer.id, return the data for that specific customer, i.e. SELECT * FROM customers WHERE id = ?
findByGroupId: Providing a group.id, return the data for all the customers (in an array of objects), belonging to that group, i.e. SELECT * FROM customers WHERE groupId = ?
Response Payloads
For each customer object, I want to return JSON like this:
findById(1);:
[{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
}]
findByGroupId(2);:
[{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
},
{
"id" : 4,
"name" : "Pete Smith",
"groupId" : 2,
"status" : "suspended",
"state" : "bad"
}]
GROUP
Data Model
id
title
Requests
I want to be able to do:
findById: Providing a single group.id, return the data for that specific group, i.e. SELECT * FROM groups WHERE id = ?
Response Payloads
For each group object, I want to return JSON like this:
findById(2);:
{
"id" : 2,
"title" : "This is Group 2",
"customers" : [{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
},
{
"id" : 4,
"name" : "Pete Smith",
"groupId" : 2,
"status" : "suspended",
"state" : "bad"
}]
}
Requirements:
Must use ES6 Classes
Each model in its own file (e.g. customer.js) to be exported
Questions:
My main questions are:
Where would I define the data structure, including fields that require data transformation, using the private methods (e.g. _getState())
Should the findById, findByGroupId, etc by defined within the scope of the class? Or, should these by separate methods (in the same file as the class), that would instantiate the object?
How should I deal with the case where one object is a child of the other, e.g. returning the Customer objects that belongs to a Group object as an array of objects in the Group's findById?
Where should the SQL queries that will connect to the DB be defined? In the getById, getByGroupId, etc?
UPDATE!!
This is what I came up with - (would be awesome if someone could review, and comment):
CUSTOMER Model
'use strict';
class Cust {
constructor (custData) {
this.id = custData.id;
this.name = custData.name;
this.groupId = custData.groupId;
this.status = custData.status;
this.state = this._getState(custData.status);
}
_getState(status) {
let state = (status == 'active' ? 'good' : 'bad');
return state;
}
}
exports.findById = ((id) => {
return new Promise ((resolve, reject) => {
let custData = `do the MySQL query here`;
let cust = new Cust (custData);
let Group = require(appDir + process.env.PATH_API + process.env.PATH_MODELS + 'group');
Group.findById(cust.groupId).then(
(group) => {
cust.group = group;
resolve (cust)
},
(err) => {
resolve (cust);
}
);
});
});
GROUP Model
'use strict';
class Group {
constructor (groupData) {
this.id = groupData.id;
this.title = groupData.title;
}
}
exports.findById = ((id) => {
return new Promise ((resolve, reject) => {
let groupData = `do the MySQL query here`;
if (id != 2){
reject('group - no go');
};
let group = new Group (groupData);
resolve (group);
});
});
CUSTOMER Controller (where the Customer model is instantiated)
'use strict';
var Cust = require(appDir + process.env.PATH_API + process.env.PATH_MODELS + 'cust');
class CustController {
constructor () {
}
getCust (req, res) {
Cust.findById(req.params.id).then(
(cust) => {
res(cust);
},
(err) => {
res(err);
}
)
}
}
module.exports = CustController;
This seems to be working well, and I've been able to use Class, Promise and let to make it more ES6 friendly.
So, I'd like to get some input on my approach. Also, am I using the export and required features correctly in this context?
Here is another approach,
Where would I define the data structure, including fields that require data transformation, using the private methods (e.g. _getState())
You should define those fields, relationship in your model class extending the top model. Example:
class Group extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
title: {
type: 'string'
}
};
}
relationships() {
return {
'Customer': {
type: 'hasMany',
foreignKey: 'groupId'
}
};
}
}
Should the findById, findByGroupId, etc by defined within the scope of the class? Or, should these by separate methods (in the same file as the class), that would instantiate the object?
Instead of having many functions use findByAttribute(attr) in Model Example:
static findByAttribute(attr) {
return new Promise((resolve, reject) => {
var query = this._convertObjectToQueriesArray(attr);
query = query.join(" and ");
let records = `SELECT * from ${this.getResourceName()} where ${query}`;
var result = this.run(records);
// Note: Only support 'equals' and 'and' operator
if (!result) {
reject('Could not found records');
} else {
var data = [];
result.forEach(function(record) {
data.push(new this(record));
});
resolve(data);
}
});
}
/**
* Convert Object of key value to sql filters
*
* #param {Object} Ex: {id:1, name: "John"}
* #return {Array of String} ['id=1', 'name=John']
*/
static _convertObjectToQueriesArray(attrs) {
var queryArray = [];
for (var key in attrs) {
queryArray.push(key + " = " + attrs[key]);
}
return queryArray;
}
/**
* Returns table name or resource name.
*
* #return {String}
*/
static getResourceName() {
if (this.resourceName) return this.resourceName();
if (this.constructor.name == "Model") {
throw new Error("Model is not initialized");
}
return this.constructor.name.toLowerCase();
}
How should I deal with the case where one object is a child of the other, e.g. returning the Customer objects that belongs to a Group object as an array of objects in the Group's findById?
In case of relationships, you should have methods like findRelations, getRelatedRecords.
var customer1 = new Customer({ id: 1, groupId: 3});
customer1.getRelatedRecords('Group');
class Model {
...
getRelatedRecords(reln) {
var targetRelationship = this.relationships()[reln];
if (!targetRelationship) {
throw new Error("No relationship found.");
}
var primaryKey = this._getPrimaryKey();
var relatedObject = eval(reln);
var attr = {};
if (targetRelationship.type == "hasOne") {
console.log(this.values);
attr[relatedObject.prototype._getPrimaryKey()] = this.values[targetRelationship.foreignKey];
} else if (targetRelationship.type == "hasMany") {
attr[targetRelationship.foreignKey] = this.values[this._getPrimaryKey()];
}
relatedObject.findByAttribute(attr).then(function(records) {
// this.values[reln] = records;
});
}
...
}
Where should the SQL queries that will connect to the DB be defined? In the getById, getByGroupId, etc?
This one is tricky, but since you want your solution to be simple put the queries inside your find methods. Ideal scenario will be to have their own QueryBuilder Class.
Check the following full code the solution is not fully functional but you get the idea. I've also added engine variable in the model which you can use to enhance fetching mechanism. All other design ideas are upto your imagination :)
FULL CODE:
var config = {
engine: 'db' // Ex: rest, db
};
class Model {
constructor(values) {
this.values = values;
this.engine = config.engine;
}
toObj() {
var data = {};
for (var key in this.values) {
if (this.values[key] instanceof Model) {
data[key] = this.values[key].toObj();
} else if (this.values[key] instanceof Array) {
data[key] = this.values[key].map(x => x.toObj());
} else {
data[key] = this.values[key];
}
}
return data;
}
static findByAttribute(attr) {
return new Promise((resolve, reject) => {
var query = this._convertObjectToQueriesArray(attr);
query = query.join(" and ");
let records = `SELECT * from ${this.getResourceName()} where ${query}`;
var result = this.run(records);
// Note: Only support 'equals' and 'and' operator
if (!result) {
reject('Could not found records');
} else {
var data = [];
result.forEach(function(record) {
data.push(new this(record));
});
resolve(data);
}
});
}
getRelatedRecords(reln) {
var targetRelationship = this.relationships()[reln];
if (!targetRelationship) {
throw new Error("No relationship found.");
}
var primaryKey = this._getPrimaryKey();
var relatedObject = eval(reln);
var attr = {};
if (targetRelationship.type == "hasOne") {
console.log(this.values);
attr[relatedObject.prototype._getPrimaryKey()] = this.values[targetRelationship.foreignKey];
} else if (targetRelationship.type == "hasMany") {
attr[targetRelationship.foreignKey] = this.values[this._getPrimaryKey()];
}
relatedObject.findByAttribute(attr).then(function(records) {
// this.values[reln] = records;
});
}
/**
* Test function to show what queries are being ran.
*/
static run(query) {
console.log(query);
return [];
}
_getPrimaryKey() {
for (var key in this.attributes()) {
if (this.attributes()[key].primary) {
return key;
}
}
}
/**
* Convert Object of key value to sql filters
*
* #param {Object} Ex: {id:1, name: "John"}
* #return {Array of String} ['id=1', 'name=John']
*/
static _convertObjectToQueriesArray(attrs) {
var queryArray = [];
for (var key in attrs) {
queryArray.push(key + " = " + attrs[key]);
}
return queryArray;
}
/**
* Returns table name or resource name.
*
* #return {String}
*/
static getResourceName() {
if (this.resourceName) return this.resourceName();
if (this.constructor.name == "Model") {
throw new Error("Model is not initialized");
}
return this.constructor.name.toLowerCase();
}
}
class Customer extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
name: {
type: 'string'
},
groupId: {
type: 'integer'
},
status: {
type: 'string'
},
state: {
type: 'string'
}
};
}
relationships() {
return {
'Group': {
type: 'hasOne',
foreignKey: 'groupId'
}
};
}
}
class Group extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
title: {
type: 'string'
}
};
}
relationships() {
return {
'Customer': {
type: 'hasMany',
foreignKey: 'groupId'
}
};
}
}
var cust = new Customer({
id: 1,
groupId: 3
});
cust.getRelatedRecords('Group');
var group = new Group({
id: 3,
title: "Awesome Group"
});
group.getRelatedRecords('Customer');
var groupData = new Group({
"id": 2,
"title": "This is Group 2",
"customers": [new Customer({
"id": 1,
"name": "John Doe",
"groupId": 2,
"status": "active",
"state": "good"
}),
new Customer({
"id": 4,
"name": "Pete Smith",
"groupId": 2,
"status": "suspended",
"state": "bad"
})
]
});
console.log(groupData.toObj());

Categories