I am using Sequelize, MySQL and Node to write a web application.
For most of my DB needs, I usually do some verification, then fetch my models (eagerly with associations) and send them back to the client, almost always as-is (at least, up to now).
I wrote a little utility function getValuesFromRows to extract the values from a returned row array:
getValuesFromRows: function(rows, valuesProp) {
// get POD (plain old data) values
valuesProp = valuesProp || 'values';
if (rows instanceof Array) {
var allValues = [];
for (var i = 0; i < rows.length; ++i) {
allValues[i] = rows[i][valuesProp];
}
return allValues;
}
else if (rows) {
// only one object
return rows[valuesProp];
}
return null;
}
// ...
...findAll(...)...complete(function(err, rows) {
var allValues = getValuesFromRows(rows);
sendToClient(errToString(err, user), allValues);
});
However, I am adding more and more complex relations to my DB models. As a result, I get more associations that I have to fetch. Now, I don't only have to call above function to get the values from each row, but also I need more complicated utilities to get the values from all included (eagerly loaded) associations. Is there a way to only get values from Sequelize queries (and not the Sequelize model instance) that also includes all associated values from the instance?
Else, I would have to manually "get all values from each Project and add one item to that values object for the values property of each entry of Project.members" (for example). Note that things get worse fast if you nest associations (e.g. members have tasks and tasks have this and that etc.).
I am guessing that I have to write such utility myself?
I found a simple solution to my problem, by extending my existing POD converting function above with a recursion into all include'd associations of the query. The Solution works with find, findAll, all and possibly other operations with non-trivial results.
Code
/**
* Get POD (plain old data) values from Sequelize results.
*
* #param rows The result object or array from a Sequelize query's `success` or `complete` operation.
* #param associations The `include` parameter of the Sequelize query.
*/
getValuesFromRows: function(rows, associations) {
// get POD (plain old data) values
var values;
if (rows instanceof Array) {
// call this method on every element of the given array of rows
values = [];
for (var i = 0; i < rows.length; ++i) {
// recurse
values[i] = this.getValuesFromRows(rows[i], associations);
}
}
else if (rows) {
// only one row
values = rows.dataValues;
// get values from associated rows
if (values && associations) {
for (var i = 0; i < associations.length; ++i) {
var association = associations[i];
var propName = association.as;
// recurse
values[propName] = this.getValuesFromRows(values[propName], association.include);
};
}
}
return values;
}
Example
var postAssociations = [
// poster association
{
model: User,
as: 'author'
},
// comments association
{
model: Comment,
as: 'comments',
include: [
{
// author association
model: User,
as: 'author'
}
]
}
];
// ...
var query = {
where: ...
include: postAssociations;
};
// query post data from DB
return Post.findAll(query)
// convert Sequelize result to POD
.then(function(posts) {
return getValuesFromRows(posts, postAssociations);
})
// send POD back to client
.then(client.sendPosts);
In the above example, client.sendPosts receives an array. Each entry of the array will have properties author and comments. Each comment in the comments array will also have an author property. The entire array only contains POD (plain old data).
Related
I'm creating a very simple CRUD app using Polymer js but facing some issues while editing the records.
Here is the code for add/edit:
_addTodo() {
if(this.user.id) {
let foundIndex = this.users.findIndex( x => x.id === this.user.id);
this.users[foundIndex] = this.user;
this.set('users', this.users);
console.log(this.users);
}
else {
this.user.id = Math.floor((Math.random() * 100000) + 1);
this.push('users', this.user);
}
this.user = {};
}
Although I could see the values inside the users object getting changed in the browser console but it's not getting changed in the DOM/UI.
If I'm using a static user object like below then it works:
_addTodo() {
if(this.user.id) {
var users = [
{
id: 1,
name: 'xyz',
age: 21
},
{
id: 2,
name: 'xyz123',
age: 5
}
]
this.set('users', users);
console.log(this.users);
}
else {
this.user.id = Math.floor((Math.random() * 100000) + 1);
this.push('users', this.user);
}
this.user = {};
}
Even I have used "notifyPath" instead of "set" but that is also not working.
Could anyone please suggest what I am doing wrong here for which the user object is not getting changed in DOM?
Update:
As suggested below, I'm using splice for updating the array but still it's not working.
JSfiddle - https://jsfiddle.net/ansumanmishra/8490y4q8/1/
this.users[foundIndex] = this.user;
this.set('users', this.users);
Updating the DOM takes performance. Whenever set is used, Polymer dirty checks every value in the array, but you have already set the array to it's new value so when it compares (basically, it compares with itself), Polymer wont detect any updates and therefor wont update the DOM.
You can't however do this as solution: var newUserArr = this.users, and then modify newUserArr because objects and arrays only create references to each other.
var a = [1]
var b = a
b[0] = 2
console.log(a) // gives [2]
You will only end up with the same thing as above: Polymer dirty checking the array with itself. Remove the reference with JSON.stringify, and then set the new array. I use this method all the time.
if(this.user.id) {
let foundIndex = this.users.findIndex( x => x.id === this.user.id);
// Remove references
var newUserArr = JSON.parse(JSON.stringify(this.users)));
newUserArr[foundIndex] = this.user;
this.set('users', newUserArr);
}
EDIT
However, when you want to edit something, you also create a reference from the object in the array, so when you type in your inputs, you will update the object in the existing array users.
I fiddled with your fiddle, and now it works. What I did was that I added JSON.parse(JSON.stringify()) in the method _editUser() too.
http://jsfiddle.net/c6h2hwch/
From "Set a property or subproperty by path": "calling set on an object property won't cause Polymer to pick up changes to the object's subproperties, unless the object itself changes." Note example:
// DOES NOT WORK
this.profile.name = Alex;
this.set('profile', this.profile);
You need to replace this.profile with a new profile object, or update the path of each individual member of profile.
This isn't an observable change:
this.users[foundIndex] = this.user;
this.set('users', this.users);
You're modifying the array that this.users points to (in a way Polymer can't detect) and then setting this.users to the same array—this.set('users', this.users) is the same operation as this.users = this.users.
You have a couple options. One is to use this.splice:
this.splice('users', foundIndex, 1, this.user);
This says, "remove 1 item at foundIndex and insert this.user in its place.
The other option is to create a copy of the array (with Array.prototype.slice—note that's slice, not splice) to make the change observable:
const nextUsers = this.users.slice();
nextUsers[foundIndex] = this.user;
this.users = nextUsers;
I recommend this.splice because it doesn't make Polymer do quite as much work when re-rendering e.g. a dom-repeat for the array.
This piece of node.js code is run against a Spark History Server API.
What its supposed to do is find any jobs where the name matches the value passed in by uuid and return the id for only that job.
What the below code actually does is if the uuid is found in any job name, the id for every job is returned.
I think this has something to do with the way I'm parsing the JSON but I'm not entirely sure.
How do I change this so it works as I would like it to?
var arrFound = Object.keys(json).filter(function(key) {
console.log("gel json[key].name" + json[key].name);
return json[key].name;
}).reduce(function(obj, key){
if (json[key].name.indexOf(uuid)) {
obj = json[key].id;
return obj;
}
reduce is the wrong method for that. Use find or filter. You can even do that in the filter callback that you already have. And then you can chain a map to that to get the id property values for each matched key:
var arrFound = Object.keys(json).filter(function(key) {
console.log("gel json[key].name " + json[key].name);
return json[key].name && json[key].name.includes(uuid);
}).map(function(key) {
return json[key].id;
});
console.log (arrFound); // array of matched id values
Note also that your use of indexOf is wrong. You need to compare that value with -1 (not found). But nowadays you can use includes which returns a boolean.
Note that with Object.values you list the objects instead of the keys, which is more interesting in your case:
var arrFound = Object.values(json).filter(function(obj) {
console.log("gel obj.name " + obj.name);
return obj.name && obj.name.includes(uuid);
}).map(function(obj) {
return obj.id;
});
console.log (arrFound); // array of matched id values
While the accepted answer provides working code, I feel it's worth pointing out that reduce is a good way to solve this problem, and to me makes more sense than chaining filter and map:
const jobs = {
1: {
id: 1,
name: 'job: 2a2912c5-9ec8-4ead-9a8f-724ab44fc9c7'
},
2: {
id: 2,
name: 'job: 30ea8ab2-ae3f-4427-8e44-5090d064d58d'
},
3: {
id: 3,
name: 'job: 5f8abe54-8417-4b3c-90f1-a7f4aad67cfb'
},
4: {
id: 4,
name: 'job: 30ea8ab2-ae3f-4427-8e44-5090d064d58d'
}
}
const matchUUID = uuid =>
(acc, job) => job.name.includes(uuid) ? [ ...acc, job.id ] : acc
const target = '30ea8ab2-ae3f-4427-8e44-5090d064d58d'
const matchTarget = matchUUID(target)
// [ 2, 4 ]
console.log(Object.values(jobs).reduce(matchTarget, []))
reduce is appropriate for these kinds of problems: taking a larger, more complex or complete value, and reducing it to the data you require. On large datasets, it could also be more efficient since you only need to traverse the collection once.
If you're Node version-constrained or don't want to use array spread, here's a slightly more 'traditional' version:
var result = Object.keys(jobs).reduce(
function (acc, key) {
if (jobs[key].name.includes(uuid)) {
acc.push(jobs[key].id)
}
return acc
},
[]
)
Note use of Object.keys, since Object.values is ES2017 and may not always be available. String.prototype.includes is ES2015, but you could always use indexOf if necessary.
I'm using the Fat Free Framework ORM Mapper functionality to insert a set of records passed from the client in an array. The Mapper function has callbacks to for aftersave which pass an array of keys and the mapper object.
I want to be able to loop through the records and use the mapper to insert the records one by one, storing the inserted record's id in an array ('resultsArray') which is set in the F3 hive in the parent function:
function myFunction (\Base $f3, $params) {
// array of records to insert
$mappedArray = json_decode( $f3->get('BODY'), true );
$f3->set('mapper', new mapper($db,'mytable'));
$mapper = $f3->get('mapper');
// create an array in the hive to store the results of the inserts
$f3->set('resultsArray', array());
// set mapper callbacks
$mapper->aftersave(function($self,$pkeys){
// update the resultsArray in the hive?
});
$recordsInArray = count($mappedArray);
// loop through the array of objects
for ($loop = 0; $loop<$recordsInArray; $loop++){
$newRecord = $mappedArray[$loop];
try{
// clear the mapper down
$mapper->reset();
// set the array in the hive
$f3->set('data', $newRecord );
$mapper->copyFrom('data');
$mapper->save();
} catch(\PDOException $e) {
// do something
exit;
}
}
echo "done";
}
Is there a way to access the resultsArray variable I set in the hive in the aftersave callback?
Thanks
Matt
Are you sure that you need to do all these things to achieve what you want?
To be able to store the IDs of inserted records and put it in the F3's hive, I would do the following:
<?php
function myFunction (\Base $f3, $params) {
// array of records to insert
$mappedArray = json_decode( $f3->get('BODY'), true );
//mapper (no need to put it into hive):
$mapper = new mapper($db,'mytable');
// array with IDs:
$resultsArray = [];
// loop through the array of objects
for ($loop = 0; $loop<count($mappedArray); $loop++){
try{
// clear the mapper down
$mapper->reset();
// map the content (no need to put it in the hive):
$mapper->copyFrom($mappedArray[$loop]);
// insert new record:
$mapper->save();
// get the ID of the inserted record and put it in the array:
$resultsArray[] = $mapper->_id;
} catch(\PDOException $e) {
// do something
exit;
}
}
// put the array of IDs in the hive:
$f3->set("newIDs", $resultsArray);
}
You can access the hive within the aftersave handler with the php use feature:
function myFunction (\Base $f3, $params) {
// ...
$mapper->aftersave(function($self,$pkeys) use($f3) {
$f3->get('resultsArray');
});
}
I have a requirement wherein I have get the document from couchbase.
Following in the Map function that I am using for the same -
function (doc, meta) {
if (meta.type == "json" && doc!=null) {
emit(doc);
}
}
There is no reduce function. Also following is my java code to get the document -
List<URI> hosts = Arrays.asList(
new URI("http://<some DNS with port>/pools")
);
// Name of the Bucket to connect to
String bucket = "Test-Sessions";
// Password of the bucket (empty) string if none
String password = "";
//System.setProperty("viewmode", "development");
// Connect to the Cluster
CouchbaseClient client = new CouchbaseClient(hosts, bucket, password);
String designDoc = "sessions";
String viewName = "by_test";
View view = client.getView(designDoc, viewName);
Query query = new Query();
query.setIncludeDocs(true);
query.setKey(String.valueOf(122));
ViewResponse result = client.query(view, query);
Object object = null;
for(ViewRow row : result) {
if(null != row) {
object = row.getDocument();
}// deal with the document/data
}
System.out.println("Object" + object);
And the data that I have in couchbase is key - "122" and value - "true". But for some reason , I do not get any rows in the ViewResponse. What is going wrong can anyone help?
I don't understand what you are trying to achieve here, you are using a view to get a document by it's key? Key == 122? Why can't you just do client.get(122) ?
If you just need a list of all the keys in your bucket (of which you can use to pull back all documents via include docs) then make your function like so:
function (doc, meta) {
if (meta.type == "json") {
emit();
}
}
The key of the document is always emitted as an ID (viewRow.getId()). You don't need to emit the document, try to emit as little data as possible to keep view sizes small.
If you are needing to manipulate all the documents in your bucket be careful as the size grows, perhaps you'd need to look at pagination to cycle through the results. http://tugdualgrall.blogspot.com.es/
Also once you have the ViewResponse loop over it like so:
for(ViewRow row : result) {
row.getDocument(); // deal with the document/data
}
You don't need to be doing checks for null on the rows.
I am using knockout and want to use arrayFilter to return array of objects where a property "modified" inside of another array has value of true.
i.e.
my json object looks like
Family tree
Family{
LastName=Smith
Children{
Name=bob,
Modified=false},
{
Name=tom, Modified=true}
}
Family{
LastName=Jones
Children{
Name=bruno,
Modified=false},
{
Name=mary, Modified=false}
}
The result of the array filter would be (as follows) becuase child tom has modified =true
FamilyTree
Family{
LastName=Smith
Children{
Name=bob,
Modified=false},
{
Name=tom, Modified=true}
}
is this possible?
I think the solution that #pax162 supplied probably answers your question. However, there is the question of usage. The proposed solution is going to perform nested iterations. If you are only expecting to be processing the data once (as opposed to driving some rich client views), this is the approach to take. On the other hand, if you are binding this data to a view, you might consider another more KO-like approach. Here's what I have in mind:
var family = function(data){
var self = {
LastName :ko.observable(data.LastName),
Children : ko.observableArray( data.Children || [])
};
family.hasModifiedChildren = ko.computed(function() {
return ko.utils.arrayFirst(this.Children(),
function(child) {
return child.Modified === true;
}) !== null;
}, family);
return self;
}
Rather than using JSON data, create observable JS objects as such:
var families = return ko.utils.arrayMap(familiesJson, family);
// or, if you need an observable:
families = ko.observableArray(ko.utils.arrayMap(familiesJson, family));
Finally, get your list of families with modified children like this:
var familiesWithModifiedChildren = ko.computed(function() {
return ko.utils.arrayFilter(families, function(fam) {
return fam.hasModifiedChildren();
});
});
If you are building a live-update page, you'll want to go with this style of view model. This will allow Knockout to utilize its observer optimizations rather than building a new array every time the function is evaluated. Hope this helps!
If you want to get only families with at least one modified child, you can do this (http://jsfiddle.net/MAyNn/3/) . The json was not valid, changed it a bit.
var families = [
{
LastName :"Smith",
Children : [{
Name:"bob",
Modified:false},
{
Name:"tom", Modified :true}
]
},
{
LastName :"Jones",
Children : [{
Name:"bruno",
Modified:false},
{
Name:"mary", Modified :false}
]
}
];
var filteredFamilies = ko.utils.arrayFilter(families, function(family){
var hasModified = false;
var first = ko.utils.arrayFirst(family.Children, function(child){
return child.Modified;
})
if (first) {
hasModified = true;
}
return hasModified;
})
console.log(families);
console.log(filteredFamilies);