I'm writing a script wherein the user selects directories, which are then stored in an array property, so that they can be recursively crawled.
{
"archives": [
"C:\\AMD\\Packages",
"C:\\Users",
"C:\\Windows",
"D:\\",
"E:\\Pictures\\Birthday"
]
}
I obviously don't want to be storing duplicate paths or paths that are contained by other paths. For example, if the user were to select a new folder to add to the array, E:\\Pictures, then E:\\Pictures\\Birthday would be discarded and replaced by it since E:\\Pictures contains E:\\Pictures\\Birthday.
{
"archives": [
"C:\\AMD\\Packages",
"C:\\Users",
"C:\\Windows",
"D:\\",
"E:\\Pictures"
]
}
I know this can be done by parsing all of the values being considered (i.e. ['C:', 'AMD', 'Packages'], [...], ... etc) and then comparing them all to one another. However, this seems extremely intensive, especially if the array of paths grows bigger and the directory paths are longer.
You could also do it by comparing the strings with includes. For example, if A includes B or B includes A, split them, and discard the one with a longer length.
for (const dir of dirs){
if (newPath.includes(dir) || dir.includes(newPath)){
if (newPath.split('\\') < dir.split('\\')){
// remove dir from json object and replace it with newPath
}
} else {
pathArray.push(dir)
}
}
After reading one of the answers below, I just realized that the includes method runs into the issue of comparing similar, yet unique paths i.e. C:\Users and C:\User.
Although there's gotta be a better way to do this??
This function will give you your desired results. It first looks to see if the parent of the path exists in the archives, and if so, does nothing. If it doesn't, it then removes any children of the path and then inserts the new path.
Update
I've added a delim input to the function to make it usable for unix/MacOS style filenames as well.
let data = {
"archives": [
"C:\\AMD\\Packages",
"C:\\Users",
"C:\\Windows",
"D:\\",
"E:\\Pictures"
]
};
const add_to_archives = (path, data, delim) => {
// does the parent of this path already exist? if so, nothing to do
if (data.archives.reduce((c, v) =>
c || path.indexOf(v.slice(-1) == delim ? v : (v + delim)) === 0, false)) return data;
// not found. remove any children of this path
data.archives = data.archives.filter(v => v.indexOf(path.slice(-1) == delim ? path : (path + delim)) !== 0);
// and add the new path
data.archives.push(path);
return data;
}
add_to_archives("E:\\Pictures\\Too", data, "\\");
console.log(data);
add_to_archives("E:\\PicturesToo", data, "\\");
console.log(data);
add_to_archives("D:\\Documents", data, "\\");
console.log(data);
add_to_archives("C:\\AMD", data, "\\");
console.log(data);
data = {
"archives": [
"/var/www/html/site",
"/etc",
"/usr/tim",
"/bin"
]
};
add_to_archives("/var/www/html/site2", data, "/");
console.log(data);
add_to_archives("/etc/conf.d", data, "/");
console.log(data);
add_to_archives("/usr", data, "/");
console.log(data);
add_to_archives("/var/www/html", data, "/");
console.log(data);
.as-console-wrapper {
max-height: 100% !important;
}
We can approach the problem by using a prefix tree
The purpose is to limit the number of paths we check for inclusion or "containment".
That approach may be useful if you have a lot of siblings (tree traversal + lookup as key for each folder).
It is overkill if you often have a root folder specified in archives
algorithm
tree = {}
foreach path
split the path in folders (one may iterate with substring but it is worth it?)
try to match folders of that path while traversing the tree
if you encounter a stop node, skip to next path
if not,
if your path end on an existing node
mark that node as a stop node
drop the children of that node (you can let them be, though)
else
include the remaining folders of the path as node in tree
mark the last node as a stop node
Implem
Note that implem below will fail if path includes a folder named "stop". By subjective order of preference
Use Map and Symbol('stop')
or a real tree (at least do not store folders alongside the boolean stop)
do not suppose any stop node and always drop children if you manage to reach the end of your path
Hope no one tries to outsmart you and rename stop as some obscure -folder will not exist- lolol_xxzz9_stop
function nodupes(archives){
tree = {};
archives.forEach(arch=>{
const folders = arch.split('\\');
folders.splice(1,1);
//case of empty string such as D:\\\
if(folders[folders.length-1].length==0){folders.pop();}
let cur = tree;
let dropped = false;
let lastFolderIndex = 0;
let ok = folders.every((folder,i)=>{
if(cur[folder]){
if(cur[folder].stop){
dropped = true;
return false;
}
cur = cur[folder];
return true;
}
cur[folder] = {}
cur = cur[folder];
lastFolderIndex = i;
return true;
});
if(ok){
cur.stop = true;
//delete (facultatively) the subfolders
if(lastFolderIndex < folders.length-1){
console.log('cleanup', folders, 'on node', cur)
Object.keys(cur).forEach(k=>{
if(k != 'stop'){
delete cur[k];
}
})
}
}
});
//console.log('tree', JSON.stringify(tree,null,1));
//dfs on the tree to get all the paths to... the leaves
let dfs = function(tree,paths,p){
if(tree.stop){
return paths.push(p.join('\\\\'));
}
Object.keys(tree).forEach(k=>{
dfs(tree[k], paths, p.concat(k));
});
}
let paths = [];
dfs(tree, paths,[]);
return paths;
}
let archives = [
'C:\\\\ab',
'D:\\\\', //just some root
'D:\\\\ab',//be dropped
'D:\\\\abc',//dropped as well
'F:\\\\abc\\\\e',//two folder creation
'C:\\\\ab\\c',
'B:\\\\ab\\c',
'B:\\\\ab',//expect B\\\\ab\\c to be dropped
]
console.log(nodupes(archives))
Try this
console.log([
"C:\\AMD\\Packages",
"C:\\Users",
"C:\\User",
"E:\\Pictures",
"E:\\Pictures\\Birthday",
"C:\\Windows",
"D:\\",
"D:\\aabbcc",
"E:\\Pictures\\Birthday"
].sort().reduce(
(acc, cur) =>
acc.length > 0
&& cur.startsWith(acc[acc.length - 1])
&& ( cur.indexOf("\\", acc[acc.length - 1].replace(/\\$/,"").length) !== -1 )
&& acc || acc.concat(cur)
, []
))
Related
I am wrting a plain .env file as following:
VAR1=VAL1
VAR2=VAL2
I wonder if there's some module I can use in NodeJS to have some effect like :
somefunction(envfile.VAR1) = VAL3
and the resulted .env file would be
VAR1=VAL3
VAR2=VAL2
i.e., with other variables unchanged, just update the selected variable.
You can use the fs, os module and some basic array/string operations.
const fs = require("fs");
const os = require("os");
function setEnvValue(key, value) {
// read file from hdd & split if from a linebreak to a array
const ENV_VARS = fs.readFileSync("./.env", "utf8").split(os.EOL);
// find the env we want based on the key
const target = ENV_VARS.indexOf(ENV_VARS.find((line) => {
return line.match(new RegExp(key));
}));
// replace the key/value with the new value
ENV_VARS.splice(target, 1, `${key}=${value}`);
// write everything back to the file system
fs.writeFileSync("./.env", ENV_VARS.join(os.EOL));
}
setEnvValue("VAR1", "ENV_1_VAL");
.env
VAR1=VAL1
VAR2=VAL2
VAR3=VAL3
Afer the executen, VAR1 will be ENV_1_VAL
No external modules no magic ;)
I think the accepted solution will suffice for most use cases, but I encountered a few problems while using it personally:
It will match keys that is prefixed with your target key if it is found first (e.g. if ENV_VAR is the key, ENV_VAR_FOO is also a valid match).
If the key does not exist in your .env file, it will replace the last line of your .env file. In my case, I wanted to do an upsert instead of just updating existing env var.
It will match commented lines and update them.
I modified a few things from Marc's answer to solve the above problems:
function setEnvValue(key, value) {
// read file from hdd & split if from a linebreak to a array
const ENV_VARS = fs.readFileSync(".env", "utf8").split(os.EOL);
// find the env we want based on the key
const target = ENV_VARS.indexOf(ENV_VARS.find((line) => {
// (?<!#\s*) Negative lookbehind to avoid matching comments (lines that starts with #).
// There is a double slash in the RegExp constructor to escape it.
// (?==) Positive lookahead to check if there is an equal sign right after the key.
// This is to prevent matching keys prefixed with the key of the env var to update.
const keyValRegex = new RegExp(`(?<!#\\s*)${key}(?==)`);
return line.match(keyValRegex);
}));
// if key-value pair exists in the .env file,
if (target !== -1) {
// replace the key/value with the new value
ENV_VARS.splice(target, 1, `${key}=${value}`);
} else {
// if it doesn't exist, add it instead
ENV_VARS.push(`${key}=${value}`);
}
// write everything back to the file system
fs.writeFileSync(".env", ENV_VARS.join(os.EOL));
}
It looks like - you want to read your current .env file, after you want to change some values and save it.
You should use the fs module from standard Node.js module library: https://nodejs.org/api/fs.html
var updateAttributeEnv = function(envPath, attrName, newVal){
var dataArray = fs.readFileSync(envPath,'utf8').split('\n');
var replacedArray = dataArray.map((line) => {
if (line.split('=')[0] == attrName){
return attrName + "=" + String(newVal);
} else {
return line;
}
})
fs.writeFileSync(envPath, "");
for (let i = 0; i < replacedArray.length; i++) {
fs.appendFileSync(envPath, replacedArray[i] + "\n");
}
}
I wrote this function to solve my issue.
Simple and it works:
for typescript
import fs from 'fs'
import os from 'os'
import path from 'path'
function setEnvValue(key: string, value: string): void {
const environment_path = path.resolve('config/environments/.env.test')
const ENV_VARS = fs.readFileSync(environment_path, 'utf8').split(os.EOL)
const line = ENV_VARS.find((line: string) => {
return line.match(`(?<!#\\s*)${key}(?==)`)
})
if (line) {
const target = ENV_VARS.indexOf(line as string)
if (target !== -1) {
ENV_VARS.splice(target, 1, `${key}=${value}`)
} else {
ENV_VARS.push(`${key}=${value}`)
}
}
fs.writeFileSync(environment_path, ENV_VARS.join(os.EOL))
}
I have an array of paths that i want to convert to an array of unique paths. Below is my attempt and i know there is a recursive call somewhere in function returnPath but don't know exactly how to implement that.
var paths = [{
out: "KFvEbaKPDC|o|0",
in: "M7gtymFAo4|i|0"
}, {
out: "KFvEbaKPDC|o|0",
in: "53s1L4YLpo|i|0"
}, {
out: "M7gtymFAo4|o|0",
in: "DEa78KAyDf|i|0"
}, {
out: "53s1L4YLpo|o|0",
in: "R6hUSNn5T5|i|0"
}]
function chainPaths(paths) {
let localCopy = paths;
const chainPaths = [];
localCopy = localCopy.filter((item) => {
if (item.out.split('|')[0] === 'KFvEbaKPDC') {
return item;
}
})
console.log(localCopy);
localCopy.forEach((c) => {
chainPaths.push(returnUniquePath(c));
})
}
function returnUniquePath(c) {
const arr = [];
arr.push(c.out);
arr.push(c.in);
//recursive call here may be??
return arr;
}
chainPaths(paths)
i am hoping to end up with an array that has unique paths like below
[[
"KFvEbaKPDC|o|0",
"M7gtymFAo4|i|0"
"M7gtymFAo4|o|0",
"DEa78KAyDf|i|0"
],[
"KFvEbaKPDC|o|0",
"53s1L4YLpo|i|0"
"53s1L4YLpo|o|0",
"R6hUSNn5T5|i|0"
]]
lets imagine 'KFvEbaKPDC' to be a parent node, this parent has output path, denoted by 'o' as in KFvEbaKPDC|o|0, to two children 'M7gtymFAo4' and '53s1L4YLpo' on their inputs denoted by 'i' and each child i.e. M7gtymFAo4' and '53s1L4YLpo' has an output path to another node. Hope that makes sense.
one can visualize unique paths as below
KFvEbaKPDC|o|0 -> M7gtymFAo4|i|0 -> M7gtymFAo4|o|0 -> DEa78KAyDf|i|0
KFvEbaKPDC|o|0 -> 53s1L4YLpo|i|0 -> 53s1L4YLpo|o|0 -> R6hUSNn5T5|i|0
I was able to treeify the paths array and then was able to traverse unique paths like
KFvEbaKPDC -> M7gtymFAo4 -> DEa78KAyDf
but that solution lacked information about inputs, outputs and indices
I'm trying to write a routing function that will return all possible routes from any two given locations (I'm calling them "spaces"), but I'm stuck on writing the recursive function.
My data will look something like this:
const allSpaces = [
{
id: 0,
name: 'Living Room',
connectedSpaces: [1,2]
},
{
id: 1,
name: 'Hallway A',
connectedSpaces: [0,4]
},
{
id: 2,
name: 'Hallway B',
connectedSpaces: [0,4]
},
{
id: 3,
name: 'Bedroom',
connectedSpaces: [1,2]
}
];
So, calling a getAllRoutes(0,3) method would walk all possible routes and return an array of arrays:
[
[0,1,3],
[0,2,3]
]
Keep in mind that this may not always be as simplistic of a dataset as my example (i.e., Hallway A could have an offshoot that provides an alternate route, or could re-intersect with previously visited spaces).
I'm pretty stumped. I've made several attempts at a recursive function but keep ending up with incomplete lists or infinite loops. Any help would be appreciated!
Visualizing your data
Whenever you find yourself stuck with a problem like this, it helps to think of an easy way to visualize what's happening. To get a feel for the graph you're working with, I wrote a few lines of code to visualize the graph.
Through the visualization, I noticed there's probably a small error in the data. I figured spaces 1 and 2 should be connected to 0 and 3 rather than 0 and 4. I adjusted this in the data, and added an additional space for testing.
If you like, you can check out the visualization by expanding the snippet below.
const allSpaces=[{id:0,name:"Living Room",connectedSpaces:[1,2]},{id:1,name:"Hallway A",connectedSpaces:[0,3,4]},{id:2,name:"Hallway B",connectedSpaces:[0,3]},{id:3,name:"Bedroom",connectedSpaces:[1,2]}, {id:4,name:"Master bedroom",connectedSpaces:[1]}];
const Edge = (() => {
// Do not support two way edges. Cache from and to internally:
const cache = new Map();
const Edge = (from, to) => {
const id = `${Math.min(from, to)}.${Math.max(from, to)}`;
const length = 1;
if (!cache.has(id)) {
cache.set(id, { from, to, id, length });
}
return cache.get(id);
}
return (from => to => Edge(from, to));
})();
const edges = uniques(allSpaces.reduce(
(edges, node) => edges.concat(
node.connectedSpaces.map(Edge(node.id))
), []
));
const Node = ({ id, name }) => ({ id, label: name });
const nodes = allSpaces.map(Node);
const network = new vis.Network(
document.getElementById("cvs"),
{ nodes, edges },
{}
);
function uniques(arr) { return Array.from(new Set(arr).values()); }
<link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis-network.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
<div id="cvs" style="height: 300px"></div>
Being able to see our data, makes it easier to check if our function works! Now, you've asked to find all paths to get from A to B. Note that this the number of possible paths can very quickly increase as you add more nodes. (e.g., have a look at the Traveling Salesman Problem).
Shortest path?
If you're really looking for the shortest path, you might want to adapt the code below to use Dijkstra's Shortest Path algorithm, as SLaks suggested in their comment.
Brute force approach
But, since the example set is small, and you asked for all routes, let's brute force it:
Define an empty collection of paths
Define a start node
Add its id to the current path
For every node it links to:
Check if it's our destination; if so: return the collection with the current path added
Check if it's already in our path; if so: skip it (we don't want to move in circles)
If it's not in our path nor our destination, add it to the path and move deeper
Or, in code:
const walk = (destination, paths, path, node) => {
// Walking in circles
if (path.includes(node.id)) // <-- expensive, for large paths use a Set
return paths;
// End reached
if (node.id === destination)
return paths.concat([path.concat(node.id)]);
// Take next step recursively
return node.connectedSpaces
.reduce(
(acc, id) => walk(destination, acc, path.concat(node.id), spaceMap.get(id)),
paths
);
}
Here's a running snippet you can use to step through and see what happens:
const allSpaces=[{id:0,name:"Living Room",connectedSpaces:[1,2]},{id:1,name:"Hallway A",connectedSpaces:[0,3,4]},{id:2,name:"Hallway B",connectedSpaces:[0,3]},{id:3,name:"Bedroom",connectedSpaces:[1,2]}, {id:4,name:"Master bedroom",connectedSpaces:[1]}];
const spaceMap = new Map(allSpaces.map(s => [s.id, s]));
const walk = (destination, paths, path, node) => {
// Walking in circles
if (path.includes(node.id)) // <-- expensive, for large paths use a Set
return paths;
// End reached
if (node.id === destination)
return paths.concat([path.concat(node.id)]);
// Take next step recursively
return node.connectedSpaces
.reduce(
(acc, id) => walk(destination, acc, path.concat(node.id), spaceMap.get(id)),
paths
);
}
const calcRoute = (from, to) => {
const routes = walk(to, [], [], spaceMap.get(from));
return `
Found ${routes.length} route(s) to ${spaceMap.get(to).name}
${routes.map(r => r.map(id => spaceMap.get(id).name).join(" -> ")).join("\n")}
`;
}
console.log(calcRoute(0, 3));
console.log(calcRoute(0, 4));
i got some sort of an issue with my custom sorting. So, basically i have this array:
[ 'src/app/account/account.js',
'src/app/account/dashboard/characters/characters.js',
'src/app/account/dashboard/characters/detail/detail.js',
'src/app/account/dashboard/dashboard.ctrl.js',
'src/app/account/dashboard/dashboard.js',
'src/app/account/dashboard/panels/admin.ctrl.js',
'src/app/account/dashboard/panels/users.ctrl.js',
'src/app/account/donate/donate.ctrl.js',
'src/app/account/donate/donate.js',
'src/app/account/settings/settings.ctrl.js',
'src/app/account/settings/settings.js',
'src/app/account/vote/vote.ctrl.js',
'src/app/account/vote/vote.js',
'src/app/membership/dialogs/login.ctrl.js',
'src/app/membership/dialogs/register.ctrl.js',
'src/app/membership/dialogs/termsOfService.ctrl.js',
'src/app/membership/membership.ctrl.js',
'src/app/membership/membership.module.js',
'src/app/news/news.ctrl.js',
'src/app/news/news.js',
'src/app/noctis.ctrl.js',
'src/app/noctis.js',
'src/app/widgets/playersOnline/playersOnline.js',
'src/app/widgets/rankings/rankings.js',
'src/app/widgets/serverDetails/serverDetails.js',
'src/common/directives/feeds/feeds.js',
'src/common/directives/panel/panel.js' ]
And what i would like that after the src/app/ the very first js that comes after the very first folder after src/app/ in our case: account, membership(can be more custom names) to be loaded first, like in the next example:
['src/app/membership/membership.module.js',
'src/app/membership/membership.ctrl.js',
'src/app/membership/dialogs/login.ctrl.js',
'src/app/membership/dialogs/register.ctrl.js',
'src/app/membership/dialogs/termsOfService.ctrl.js',]
Can you guys help me with some code for my needs? src/app will always be a fixed name except of the next directory that comes after src/app/.
Basically what comes after the unknown name of the directory after src/app, the sub directories in our case(dialogs) or can be something else like(detail, detail/character), to be loaded latest no matter what.
Basically this is the whole function:
function sortJSFiles(files) {
var src = [];
var vendor = [];
files.forEach(function(item) {
if (item.startsWith('src')) {
src.push(item);
} else {
vendor.push(item);
}
});
src.sort(function(a, b) {
var replace = ['noctis.js', 'noctis.ctrl.js', '.module.js', '.ctrl.js'];
function replaceCB(previousValue, currentValue, currentIndex, array) {
return previousValue.replace(currentValue, currentIndex);
}
return replace.reduce(replaceCB, a).localeCompare(replace.reduce(replaceCB, b));
});
return vendor.concat(src);
}
What it does, is that in paramater files comes a lot of paths with js files and i'm trying to sort them after my rule. The problem is, for example taking membership example:
['src/app/membership/dialogs/login.ctrl.js',
'src/app/membership/dialogs/register.ctrl.js',
'src/app/membership/dialogs/termsOfService.ctrl.js',
'src/app/membership/membership.module.js',
'src/app/membership/membership.ctrl.js']
It succesffully change the sort like loading *.js files that starts first with .module.js and than with .ctrl.js but there is a problem in my code that i need that any js file that comes after src/app/somefolder to be loaded first and any subfolders that are in that somefolder to be loaded latest no matter what.
I am not sure I understood you correctly (it would have been nice if you would have added the literal expected output for your sample data).
I think you want to have the folders sorted, but within the same folder, you want the files in there to be sorted before any of the subfolders in that same folder. And this should be true at every nested level.
To get the files sorted first in every folder, you should in fact extract the folders only, and sort those, and only when two items have exactly the same folder sequence, sort by the file name.
This you can do as follows:
src = src.map(function (path) {
var i = path.lastIndexOf('/');
return [path.substr(0, i), path.substr(i)];
}).sort(function (a, b) {
var i = +(a[0] == b[0]);
return a[i].localeCompare(b[i]);
}).map(function (pair) {
return pair[0] + pair[1];
});
var src = [ 'src/app/account/account.js',
'src/app/account/dashboard/characters/characters.js',
'src/app/account/dashboard/characters/detail/detail.js',
'src/app/account/dashboard/dashboard.ctrl.js',
'src/app/account/dashboard/dashboard.js',
'src/app/account/dashboard/panels/admin.ctrl.js',
'src/app/account/dashboard/panels/users.ctrl.js',
'src/app/account/donate/donate.ctrl.js',
'src/app/account/donate/donate.js',
'src/app/account/settings/settings.ctrl.js',
'src/app/account/settings/settings.js',
'src/app/account/vote/vote.ctrl.js',
'src/app/account/vote/vote.js',
'src/app/membership/dialogs/login.ctrl.js',
'src/app/membership/dialogs/register.ctrl.js',
'src/app/membership/dialogs/termsOfService.ctrl.js',
'src/app/membership/membership.ctrl.js',
'src/app/membership/membership.module.js',
'src/app/news/news.ctrl.js',
'src/app/news/news.js',
'src/app/noctis.ctrl.js',
'src/app/noctis.js',
'src/app/widgets/playersOnline/playersOnline.js',
'src/app/widgets/rankings/rankings.js',
'src/app/widgets/serverDetails/serverDetails.js',
'src/common/directives/feeds/feeds.js',
'src/common/directives/panel/panel.js' ];
src = src.map(function (path) {
var i = path.lastIndexOf('/');
return [path.substr(0, i), path.substr(i)];
}).sort(function (a, b) {
var i = +(a[0] == b[0]);
return a[i].localeCompare(b[i]);
}).map(function (pair) {
return pair[0] + pair[1];
});
console.log(src);
Explanation of the sort callback function
The sort callback function will receive argument a and b. Each of them is a pair including a path at index 0, and a filename at index 1.
The callback uses a variable i that is intended to get the value 0 or 1. If the paths of a and b are the same, then i will be 1, else it will be 0. It determines whether a comparison is needed on the paths or on the filenames.
The unitary + is used to convert the boolean expression a[0] == b[0] to a number. The conversion is: true=>1, false=>0.
I'm using the latest version of ember-cli, ember-data, ember-localstorage-adapter, and ember.
I have a Node object which has a parent and children. Since I had issues with creating multiple relationships with the same type of object, I decided to store the parentID in a string, and the childIDs in an array of strings. However, when I create a new Node and try to add the new Node's to the parents array of IDs, the ID ends up being added to the correct parent, but also other parents.
level 1 0
/ \
level 2 1 2
| |
level 3 3 4
In a structure like this, 0, 1, and 2 all have correct child and parent IDs. However, after adding 3 and 4, node 1 and node 2's childIDs are [3, 4], instead of [3], [4] respectively.
The Array attribute:
var ArrayTransform = DS.Transform.extend({
serialize: function(value) {
if (!value) {
return [];
}
return value;
},
deserialize: function(value) {
if (!value) {
return [];
}
return value;
}
});
The insertNode code:
insert: function(elem) {
var i,
_store = elem.node.store,
newNodeJSON = elem.node.serialize();
newNodeJSON.childIds = [];
newNodeJSON.level = getNextLevel();
_store.filter('node', function(node) {
return node.get('level') === newnodeJSON.level-1;
}).then(function(prevLevelNodes) {
// if no other nodes yet
if (prevLevelNodes.toArray().length === 0) {
makeNewNode(_store, newNodeJSON, elem.node);
}
// else, generates however many nodes that are in the previous level
else {
prevLevelNodes.toArray().forEach(function(node, idx) {
newNodeJSON.parentId = node.get('id');
makeNewNode(_store, newNodeJSON, elem.node);
});
}
});
}
var makeNewNode = function(_store, newNodeJSON, node) {
console.log(newNodeJSON.parentId); // returns correct value
var newNode = _store.createRecord('node', newNodeJSON);
newNode.save();
var newNodeId = newNode.get('id');
if (newNode.get('parentId')) {
_store.find('node', newNode.get('parentId')).then(function(n) {
var cids = n.get('childIds');
console.log(newNodeId); // returns expected value
console.log(cids); // **DOESN'T RETURN AN EMPTY ARRAY**: returns array with [3,4]
cids.push(newNodeId);
console.log(n.get('childIds')); // returns array with [3,4]
n.save();
});
}
To top this off, this error happens 90% of the time, but 10% of the time it performs as expected. This seems to suggest that there's some sort of race condition, but I'm not sure where that would even be. Some places that I feel like might be causing issues: the ember-cli compilation, passing the entire _store in when making a new node, ember-data being weird, ember-localstorage-adapter being funky... no clue.
For anyone else who may have this problem in the future: the problem lies in two things.
In ArrayTransform, typically I am returning the value sans modification.
In my insert code, I'm passing the same JSON that I defined at the top of the function to makeNewNode.
This JSON contains a reference to a single childIds array; therefore, each new node that gets created uses this same reference for its childIds. Although this doesn't quite explain why the cids array wasn't empty before the push executed (perhaps this is some sort of compiler oddity or console printing lag), it explains why these both Level 3 children were in both Level 2 parents' childIds array.
tl;dr: pass by value vs pass by reference error