Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Create individual relationships

  • Loading branch information...
commit 4161ca2a84d1b40d691eee88e0d29278ccd05370 1 parent 124e3d7
@safwank authored
View
BIN  Graph_Relations_v2.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
22 common/redisutil.js
@@ -1,29 +1,33 @@
// TODO: Switch between local Redis and Redis To Go (Heroku)
var redis = require('redis'),
- redisClient = redis.createClient();
+ redisClient = redis.createClient();
var TOTAL_NODES_KEY = 'total_nodes_imported';
exports.incrementTotalNodesBy = function(value) {
- redisClient.incrby(TOTAL_NODES_KEY, value, redis.print);
+ redisClient.incrby(TOTAL_NODES_KEY, value, redis.print);
}
exports.incrementTotalNodes = function() {
- redisClient.incr(TOTAL_NODES_KEY);
+ redisClient.incr(TOTAL_NODES_KEY);
}
exports.decrementTotalNodesBy = function(value) {
- redisClient.decrby(TOTAL_NODES_KEY, value);
+ redisClient.decrby(TOTAL_NODES_KEY, value);
}
exports.decrementTotalNodes = function() {
- redisClient.decr(TOTAL_NODES_KEY, redis.print);
+ redisClient.decr(TOTAL_NODES_KEY, redis.print);
+}
+
+exports.resetTotalNodes = function() {
+ redisClient.set(TOTAL_NODES_KEY, 0, redis.print);
}
exports.getTotalNodes = function(callback) {
- redisClient.get('total_nodes_imported', function(err, reply) {
- if (err) return callback(err);
+ redisClient.get('total_nodes_imported', function(err, reply) {
+ if (err) return callback(err);
- callback(null, reply);
- });
+ callback(null, reply);
+ });
}
View
102 models/individual.js
@@ -1,25 +1,22 @@
// Initialization
-
-var util = require('../common/util'),
- redisUtil = require('../common/redisutil'),
- csv = require('csv'),
- neo4j = require('neo4j'),
- db = new neo4j.GraphDatabase(process.env.NEO4J_URL || 'http://localhost:7474');
+var sys = require('sys'),
+ util = require('../common/util'),
+ redisUtil = require('../common/redisutil'),
+ csv = require('csv'),
+ neo4j = require('neo4j'),
+ db = new neo4j.GraphDatabase(process.env.NEO4J_URL || 'http://localhost:7474');
// Constants
-
var INDEX_NAME = 'nodes';
var INDEX_KEY = 'type';
var INDEX_VAL = 'individual';
// Private "constructors"
-
var Individual = module.exports = function Individual(_node) {
this._node = _node;
-};
+ };
// Pass-through Individual properties
-
util.proxyProperty(Individual, 'Id', true);
util.proxyProperty(Individual, 'ReportTo', true);
util.proxyProperty(Individual, 'Title', true);
@@ -62,36 +59,91 @@ function handleCreated(error, data) {
}
// Public functions
-
Individual.loadFromCSV = function(csvFilePath) {
- csv().fromPath(csvFilePath, { columns: true, trim: true })
- .on('data', loadIndividual)
- .on('end', handleSuccess)
- .on('error', handleLoadError);
+ csv().fromPath(csvFilePath, {
+ columns: true,
+ trim: true
+ }).on('data', loadIndividual).on('end', handleSuccess).on('error', handleLoadError);
};
-Individual.create = function (data, callback) {
+Individual.create = function(data, callback) {
var node = db.createNode(data);
var individual = new Individual(node);
-
- node.save(function (err) {
+
+ node.save(function(err) {
if (err) return callback(err);
-
- node.index(INDEX_NAME, INDEX_KEY, INDEX_VAL, function (err) {
+
+ node.index(INDEX_NAME, INDEX_KEY, INDEX_VAL, function(err) {
if (err) return callback(err);
callback(null, individual);
});
});
};
-Individual.getAll = function (callback) {
- db.getIndexedNodes(INDEX_NAME, INDEX_KEY, INDEX_VAL, function (err, nodes) {
+Individual.getAll = function(callback) {
+ db.getIndexedNodes(INDEX_NAME, INDEX_KEY, INDEX_VAL, function(err, nodes) {
if (err) return callback(null, []);
- var individuals = nodes.map(function (node) {
- return new Individual(node);
+ var individuals = nodes.map(function(node) {
+ return new Individual(node);
});
-
+
callback(null, individuals);
});
+};
+
+Individual.getById = function(id, callback) {
+ var query = [
+ 'START person=node:INDEX_NAME(INDEX_KEY="INDEX_VAL")',
+ 'WHERE person.Id="PERSON_ID"',
+ 'RETURN person',
+ 'LIMIT 1'
+ ].join('\n')
+ .replace('INDEX_NAME', INDEX_NAME)
+ .replace('INDEX_KEY', INDEX_KEY)
+ .replace('INDEX_VAL', INDEX_VAL)
+ .replace('PERSON_ID', id);
+
+ var params = { personId: id };
+
+ db.query(query, params, function(err, results) {
+ if (err) return callback(err);
+
+ var person = results[0] && results[0]['person'];
+ callback(null, new Individual(person));
+ });
+};
+
+Individual.relateTwoIndividuals = function(personId1, personId2, relationshipType, callback) {
+ /*
+ var query = [
+ 'START person1=node:INDEX_NAME(INDEX_KEY="INDEX_VAL"), person2=node:INDEX_NAME(INDEX_KEY="INDEX_VAL")',
+ 'RELATE person1-[r:IS_RELATED_TO {type:"{relationshipType}"}]->person2',
+ 'WHERE (person1.Id="{personId1}" AND person2.Id="{personId2}")'
+ 'RETURN relationship'
+ ].join('\n')
+ .replace('INDEX_NAME', INDEX_NAME)
+ .replace('INDEX_KEY', INDEX_KEY)
+ .replace('INDEX_VAL', INDEX_VAL);
+
+ var params = {
+ personId1: personId1,
+ personId2: personId2,
+ relationshipType: relationshipType
+ };
+
+ db.query(query, params, function(err, results) {
+ if (err) return callback(err);
+
+ var relationship = results[0] && results[0]['relationship'];
+ callback(null, relationship);
+ });*/
+
+ Individual.getById(personId1, function(error, person1) {
+ Individual.getById(personId2, function(error, person2) {
+ person1._node.createRelationshipTo(person2._node, relationshipType, {}, function (error, relationship) {
+ callback(error, relationship);
+ });
+ });
+ });
};
View
1  node_modules/array-query/.idea/.name
@@ -0,0 +1 @@
+array-query
View
9 node_modules/array-query/.idea/array-query.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
+
View
5 node_modules/array-query/.idea/encodings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
+</project>
+
View
25 node_modules/array-query/.idea/misc.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectResources">
+ <default-html-doctype>http://www.w3.org/1999/xhtml</default-html-doctype>
+ </component>
+ <component name="ProjectRootManager" version="2" />
+ <component name="SvnConfiguration" maxAnnotateRevisions="500">
+ <option name="USER" value="" />
+ <option name="PASSWORD" value="" />
+ <option name="mySSHConnectionTimeout" value="30000" />
+ <option name="mySSHReadTimeout" value="30000" />
+ <option name="LAST_MERGED_REVISION" />
+ <option name="MERGE_DRY_RUN" value="false" />
+ <option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
+ <option name="UPDATE_LOCK_ON_DEMAND" value="false" />
+ <option name="IGNORE_SPACES_IN_MERGE" value="false" />
+ <option name="DETECT_NESTED_COPIES" value="true" />
+ <option name="CHECK_NESTED_FOR_QUICK_MERGE" value="false" />
+ <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
+ <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
+ <option name="FORCE_UPDATE" value="false" />
+ <myIsUseDefaultProxy>false</myIsUseDefaultProxy>
+ </component>
+</project>
+
View
9 node_modules/array-query/.idea/modules.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/array-query.iml" filepath="$PROJECT_DIR$/.idea/array-query.iml" />
+ </modules>
+ </component>
+</project>
+
View
5 node_modules/array-query/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+<component name="DependencyValidationManager">
+ <state>
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+ </state>
+</component>
View
7 node_modules/array-query/.idea/vcs.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project>
+
View
243 node_modules/array-query/.idea/workspace.xml
@@ -0,0 +1,243 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ChangeListManager">
+ <list default="true" id="93979e66-a6e7-431f-9638-b6c992588df7" name="Default" comment="">
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/package.json" afterPath="$PROJECT_DIR$/package.json" />
+ </list>
+ <ignored path="array-query.iws" />
+ <ignored path=".idea/workspace.xml" />
+ <option name="TRACKING_ENABLED" value="true" />
+ <option name="SHOW_DIALOG" value="false" />
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+ <option name="LAST_RESOLUTION" value="IGNORE" />
+ </component>
+ <component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
+ <component name="CreatePatchCommitExecutor">
+ <option name="PATCH_PATH" value="" />
+ </component>
+ <component name="DaemonCodeAnalyzer">
+ <disable_hints />
+ </component>
+ <component name="FavoritesManager">
+ <favorites_list name="array-query" />
+ </component>
+ <component name="FileEditorManager">
+ <leaf />
+ </component>
+ <component name="FindManager">
+ <FindUsagesManager>
+ <setting name="OPEN_NEW_TAB" value="false" />
+ </FindUsagesManager>
+ </component>
+ <component name="Git.Settings">
+ <option name="CHECKOUT_INCLUDE_TAGS" value="false" />
+ <option name="UPDATE_CHANGES_POLICY" value="STASH" />
+ <option name="LINE_SEPARATORS_CONVERSION" value="ASK" />
+ </component>
+ <component name="IdeDocumentHistory">
+ <option name="changedFiles">
+ <list>
+ <option value="$PROJECT_DIR$/lib/query.js" />
+ <option value="$PROJECT_DIR$/README.md" />
+ <option value="$PROJECT_DIR$/package.json" />
+ </list>
+ </option>
+ </component>
+ <component name="ProjectFrameBounds">
+ <option name="y" value="22" />
+ <option name="width" value="1920" />
+ <option name="height" value="1174" />
+ </component>
+ <component name="ProjectLevelVcsManager" settingsEditedManually="false">
+ <OptionsSetting value="true" id="Add" />
+ <OptionsSetting value="true" id="Remove" />
+ <OptionsSetting value="true" id="Checkout" />
+ <OptionsSetting value="true" id="Update" />
+ <OptionsSetting value="true" id="Status" />
+ <OptionsSetting value="true" id="Edit" />
+ <ConfirmationsSetting value="0" id="Add" />
+ <ConfirmationsSetting value="0" id="Remove" />
+ </component>
+ <component name="ProjectReloadState">
+ <option name="STATE" value="0" />
+ </component>
+ <component name="ProjectView">
+ <navigator currentView="ProjectPane" proportions="" version="1" splitterProportion="0.5">
+ <flattenPackages />
+ <showMembers />
+ <showModules />
+ <showLibraryContents ProjectPane="false" />
+ <hideEmptyPackages />
+ <abbreviatePackageNames />
+ <autoscrollToSource />
+ <autoscrollFromSource />
+ <sortByType />
+ </navigator>
+ <panes>
+ <pane id="ProjectPane">
+ <subPane>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="array-query" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="array-query" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="array-query" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="array-query" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="lib" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ </subPane>
+ </pane>
+ <pane id="Scope" />
+ </panes>
+ </component>
+ <component name="PropertiesComponent">
+ <property name="WebServerToolWindowFactoryState" value="true" />
+ </component>
+ <component name="PublishConfig">
+ <servers>
+ <server id="097f0899-74d9-4618-8709-b70253925977">
+ <serverdata>
+ <mappings>
+ <mapping local="$PROJECT_DIR$" />
+ </mappings>
+ </serverdata>
+ </server>
+ </servers>
+ </component>
+ <component name="RecentsManager">
+ <key name="CopyFile.RECENT_KEYS">
+ <recent name="$PROJECT_DIR$/lib" />
+ <recent name="$PROJECT_DIR$" />
+ </key>
+ </component>
+ <component name="RunManager">
+ <list size="0" />
+ </component>
+ <component name="ShelveChangesManager" show_recycled="false" />
+ <component name="TaskManager">
+ <task active="true" id="Default" summary="Default task">
+ <changelist id="93979e66-a6e7-431f-9638-b6c992588df7" name="Default" comment="" />
+ <created>1327769480932</created>
+ <updated>1327769480932</updated>
+ </task>
+ <servers />
+ </component>
+ <component name="ToolWindowManager">
+ <frame x="0" y="22" width="1920" height="1174" extended-state="6" />
+ <editor active="false" />
+ <layout>
+ <window_info id="Changes" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+ <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
+ <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="true" content_ui="tabs" />
+ <window_info id="Remote Host" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+ <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.25320512" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+ <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
+ <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
+ <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+ <window_info id="JsTestDriver Server" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+ <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+ <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+ <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
+ <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+ </layout>
+ </component>
+ <component name="VcsContentAnnotationSettings">
+ <option name="myLimit" value="2678400000" />
+ </component>
+ <component name="VcsManagerConfiguration">
+ <option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true" />
+ <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" />
+ <option name="CHECK_NEW_TODO" value="true" />
+ <option name="myTodoPanelSettings">
+ <value>
+ <are-packages-shown value="false" />
+ <are-modules-shown value="false" />
+ <flatten-packages value="false" />
+ <is-autoscroll-to-source value="false" />
+ </value>
+ </option>
+ <option name="PERFORM_UPDATE_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_COMMIT_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_EDIT_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true" />
+ <option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false" />
+ <option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false" />
+ <option name="ENABLE_BACKGROUND_PROCESSES" value="false" />
+ <option name="CHANGED_ON_SERVER_INTERVAL" value="60" />
+ <option name="SHOW_ONLY_CHANGED_IN_SELECTION_DIFF" value="true" />
+ <option name="CHECK_COMMIT_MESSAGE_SPELLING" value="true" />
+ <option name="DEFAULT_PATCH_EXTENSION" value="patch" />
+ <option name="SHORT_DIFF_HORISONTALLY" value="true" />
+ <option name="SHORT_DIFF_EXTRA_LINES" value="2" />
+ <option name="SOFT_WRAPS_IN_SHORT_DIFF" value="true" />
+ <option name="INCLUDE_TEXT_INTO_PATCH" value="false" />
+ <option name="INCLUDE_TEXT_INTO_SHELF" value="false" />
+ <option name="CREATE_PATCH_EXPAND_DETAILS_DEFAULT" value="true" />
+ <option name="FORCE_NON_EMPTY_COMMENT" value="false" />
+ <option name="LAST_COMMIT_MESSAGE" />
+ <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" />
+ <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false" />
+ <option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false" />
+ <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false" />
+ <option name="REFORMAT_BEFORE_FILE_COMMIT" value="false" />
+ <option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8" />
+ <option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5" />
+ <option name="ACTIVE_VCS_NAME" />
+ <option name="UPDATE_GROUP_BY_PACKAGES" value="false" />
+ <option name="UPDATE_GROUP_BY_CHANGELIST" value="false" />
+ <option name="SHOW_FILE_HISTORY_AS_TREE" value="false" />
+ <option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6" />
+ </component>
+ <component name="XDebuggerManager">
+ <breakpoint-manager />
+ </component>
+ <component name="editorHistoryManager">
+ <entry file="file://$PROJECT_DIR$/README.md">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="354" column="25" selection-start="11323" selection-end="11323" vertical-scroll-proportion="0.21450152">
+ <folding />
+ </state>
+ </provider>
+ <provider editor-type-id="MarkdownPreviewEditor">
+ <state />
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/query.js">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="251" column="17" selection-start="7149" selection-end="7149" vertical-scroll-proportion="0.35019454">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/package.json">
+ <provider selected="true" editor-type-id="text-editor">
+ <state line="13" column="31" selection-start="499" selection-end="499" vertical-scroll-proportion="0.18968871">
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </component>
+</project>
+
View
411 node_modules/array-query/README.md
@@ -0,0 +1,411 @@
+query
+=====
+
+Provides an interface to pull objects out of a JavaScript array with minimal code. Useful for Backbone collections and similar scenarios. Can be used in the browser and on the server (node.js).
+
+## Why?
+
+Though you can do everything query provides you with the built-in Array methods (forEach, map, filter, etc), query can make it much more readable and concise.
+
+## Install
+
+```
+npm install array-query
+```
+
+Now you can use it in your node.js project.
+
+```js
+var query = require('array-query');
+
+var firstJacob = query('name').startsWith('Jacob').on(users).pop();
+```
+
+Or just add the query.js file to your web page for use in your client-side JavaScript.
+
+## querying
+
+Finding objects in an array with query is easy. The API takes a property name first, then checks the value, then you can continue to add properties and checks until the `on` method is called with the array you are querying.
+
+It is probably easier to see it in action. The following will return all users with the name Bob.
+
+```js
+var allBobs = query('firstName').is('Bob').on(users);
+```
+
+### query Chaining
+
+When you call `query()` it creates a new query object which returns a reference to itself. In fact, most methods of the query object return a reference to itself enabling method chaining. For example, the following three queries are all the same.
+
+```js
+var q1 = query("author");
+q1.is("Terence Hanbury White");
+q1.and("title");
+q1.is("The Once and Future King");
+
+var q2 = query("author").is("Terence Hanbury White");
+q2.and("title").is("The Once and Future King");
+
+var q3 = query("author").is("Terence Hanbury White").and("title").is("The Once and Future King");
+
+if (q1.toString() == q2.toString() == q3.toString()) alert("They're all the same!");
+```
+
+More examples:
+
+```js
+var whiteBooks = query("author").is("Terence Hanbury White").on(books);
+
+var theBooks = query("title").startsWith("The").on(books);
+
+var bigBooks = query("pages").gt(500).on(books);
+
+var topTenBiggestBooks = query().sort("pages").numeric().desc().limit(10).on(books);
+```
+
+### Select
+
+If you'd rather start with the Array you may use the slightly different select().
+
+```js
+var aBooks = select(books).where("title").startsWith("A").end();
+```
+
+The two differences between `query` and `select` is that:
+
+ 1. `query` ends with the array (e.g. `.on(books)`) and `select` starts with it (e.g. `select(books)`)
+ 2. `select` needs to know when to be done chaining and to return the results, so it ends with `end()`
+
+## Basics
+
+### query
+
+`query` is the start of our query and may optionally take the first field we want to filter by. The `query` method does not need to take a field if you only want to sort or limit the objects.
+
+```js
+query().sort("lastName").limit(20).on(users);
+
+query("age").gt(20).on(users);
+```
+
+The first query listed here shows using `query()` without a parameter. It sorts by lastName and limits the results to 20 objects. The second query gets all the objects where age is greater than 20.
+
+### and, or
+
+query provides the ability to use `and()` and `or()` in putting together your query. These usually take a parameter, which can either be a field name or another query object. The field name is only the beginning of an expression and when used should be followed up with another method call such as `equals()`, `gt()`, etc.
+
+```js
+query("username").equals("test").or("password").equals("test").on(users);
+```
+
+This looks up all objects whose username or password is "test".
+
+Query objects may be used inside the methods `and()` and `or()` to provide subqueries. This is like putting parenthesis around the expression.
+
+```js
+var notMiddleAged = query("firstName").equals("John").and(query("age").lt(20).or("age").gt(60)));
+```
+
+This query allows us to find all objects where the `firstName` is John and the age is either less than 20 or more than 60. We are unable to do this kind of sub-querying with the object-based API.
+
+## not
+
+The `not()` method can be used in an expression to negate the results.
+
+```js
+query("age").not().gt(20).and("eyeColor").not().equals("blue").on(users);
+```
+
+This query will get every object where age is not more than 20 and eye color is not blue.
+
+## Operations
+
+
+### is, equals
+
+`equals` is the most basic. The query should just be the value you want to match.
+
+```js
+query("firstName").is("John").on(users);
+
+query("lastName").equals("Smith").on(users);
+```
+
+This will match all objects where property `firstName` equals "John". `is` and `equals` are synonymous.
+
+### within
+
+`within` tests whether the object's value is within a provided array of values.
+
+```js
+query("firstName").within([ "John", "Jacob", "Jingle", "Heimer" ]).on(users);
+```
+
+This will match all objects whose firstName is "John", "Jacob", "Jingle", or "Heimer".
+
+### has
+
+`has` matches objects which have the provided value in an array.
+
+```js
+db.add({ colors: [ "red", "yellow", "blue" ] });
+
+query("colors").has("red").on(users);
+```
+
+This will match the previously added object since it's colors array *has* the value "red". Note that if on the stored objects, colors is null or an empty array, it will not match since it doesn't have "red" in the colors array.
+
+### hasAll
+
+`hasAll` matches objects which have all the provided values in an array.
+
+```js
+query("colors").hasAll(["red, "blue"]).on(users);
+```
+
+This will match all objects which have both "red" and "blue" in their colors array.
+
+### startsWith
+
+`startsWith` matches the beginning of a value.
+
+```js
+query("firstName").startsWith("J").on(users);
+```
+
+This will match all objects whose `firstName` begins with "J".
+
+### endsWith
+
+`endsWith` matches the end of a value.
+
+```js
+query("lastName").endsWith("son").on(users);
+```
+
+This will match all objects whose `lastName` ends with "son".
+
+### gt
+
+`gt` matches objects whose value is greater than what's provided. Dates are supported.
+
+```js
+query("age").gt(20).on(users);
+```
+
+This will match objects with `age` greater than 20;
+
+### gte
+
+`gte` matches objects whose value is greater than or equal to what's provided. Dates are supported.
+
+```js
+query("age").gte(20).on(users);
+```
+
+This will match objects with `age` greater than or equal to 20;
+
+### lt
+
+`lt` matches objects whose value is less than what's provided. Dates are supported.
+
+```js
+query("age").lt(20).on(users);
+```
+
+This will match objects with `age` less than 20;
+
+### lte
+
+`lte` matches objects whose value is less than or equal to what's provided. Dates are supported.
+
+```js
+query("age").lte(20).on(users);
+```
+
+This will match objects with `age` less than or equal to 20;
+
+### regex
+
+`regex` matches objects whose values match the provided regular expression.
+
+```js
+query("name").regex(/[^\w\s]/).on(users);
+```
+
+This will match objects that have a non word-or-space character in the `name` property.
+
+### same
+
+`same` matches objects where the value is the same when serialized into JSON. This allows arrays or objects to be matched without a reference to the original.
+
+```js
+users.push({ name: { first: "John", last: "Smith" }, age: 30 });
+
+query("name").same({ first: "John", last: "Smith" }).on(users);
+```
+
+This will match the added object since the `name` value is the same even if it isn't the exact instance in memory. Note that this uses the serialized JSON representation of both objects to compare. Dates should work with this method but hasn't been tested cross-browser.
+
+### type
+
+`type` matches objects where the object or property is of a given type. Valid types are a string of: object, array, number, boolean, null, undefined. Or an instance of a class (e.g. Date). If no property name is passed into the `query()`, `and()`, or `or()` methods then the type will match against the object itself rather than a property.
+
+```js
+query("age").type("number").on(users);
+
+query("published").type(Date).on(users);
+
+query("pet").type(Dog).on(users);
+
+query().type(User).or().type(Person).on(users); // matches if the object is and instance of User or Person (or a subclass thereof)
+```
+
+The first call will match all objects with a number for the age. The second call will match all objects where published is an instance of Date. The third call will match all `User` and `Person` objects in the database.
+
+### filter
+
+`filter` allows a custom filter function to be run against the value of a property or the object as a whole. If the function returns true, the object is added to the query results.
+
+```js
+query("firstName").filter(function(name) {
+ return name.toLowerCase().charAt(0) === "a";
+}).on(users);
+
+query().filter(function(obj) {
+ if (obj instanceof User) {
+ return obj.active;
+ } else if (obj instanceof Person) {
+ return obj.trustLevel === "trusted";
+ } else {
+ return false;
+ }
+}).on(users);
+```
+
+The first query here uses a custom function to match against the value of the `firstName` property of every object. The second query uses a custom function to use custom logic to match against every object because no property name was passed into the `query()` function.
+
+### search
+
+`search` matches all objects with a full-text search on the given field.
+
+```js
+query('bio').search("looking for all of these words").on(users);
+```
+
+This will match any objects which have the provided words in their `bio` field.
+
+### sort
+
+Sorts the returned results by property. Additional sort methods may follow a sort to define it further: `asc()`, `desc()`, `regular()`, `numeric()`, `date()`, and `custom()`. The default sort uses `asc()` and `regular()`, so these don't need to be used explicitly. Custom allows sorting on a property or on the object as a whole.
+
+```js
+query("active").is(true).sort("lastName").on(users);
+
+query().sort("lastName").desc().on(users);
+
+query().sort("publishedDate").date().desc().sort("title").on(users);
+
+query().sort("age").custom(function(age1, age2) {
+ if (age1 < age2) return -1;
+ else if (age1 > age2) return 1;
+ else return 0;
+}).on(users);
+
+query().sort().custom(function(obj1, obj2) {
+ return obj1.age - obj2.age;
+}).on(users);
+```
+
+The first query sorts by `lastName` after selecting only active objects. The second query sorts all objects by `lastName` in descending or reverse order. The third query sorts by `publishDate` with most recent first, then by title for dates that are the same. The fourth query uses a custom sort on the age property. The last query uses a custom sort on the object as a whole.
+
+### limit
+
+Limit the results returned.
+
+```js
+query().limit(10).on(users);
+```
+
+This returns 10 objects from the top of the array.
+
+```js
+query().sort('noisy').limit(10).on(users);
+```
+
+This returns 10 noisiest users (whatever that might mean).
+
+### offset
+
+Works with limit to select an offset which to start your limit at. This is used mostly for pagination.
+
+```js
+query().limit(10).offset(100).on(users);
+```
+
+This returns 10 objects starting at the 100th object.
+
+
+### Complex properties/fields
+
+Query fields can be dot-delimited to match sub-properties. They may even use methods. Note that the query object does not check to ensure whether the property is null, so if it is on some objects but not others you'll want to check for that first.
+
+```js
+users.push({ name: "Bob", colors: [ "red", "yellow", "blue" ] });
+
+query("colors.length").is(3).on(users);
+query("colors.length").gt(2).on(users);
+query("colors.length").lte(3).on(users);
+```
+
+These will *all* match the added object because the length is equal to three, greater than two, and less than or equal to three.
+
+```js
+users.push({ colors: [ "red", "yellow", "blue" ] });
+users.push({ colors: null });
+
+query("colors.length").is(0).on(users); // will not match the newly added object because it is null
+
+query("colors").is(null).or("colors.length").is(0).on(users); // this is how you should check
+```
+
+To check for a name without respect to case you might do the following.
+
+```js
+query('firstName.toLowerCase()').is('bob');
+
+query().sort('lastName.toLowerCase()');
+```
+
+The second query above will sort by last name irrespective of casing.
+
+## Backbone Support and Adding query to Backbone.Collections
+
+query was built with Backbone in mind. Though you may use `query("get('firstName')").is("John")` to effectively work with
+Backbone models, query allows you to shorten that to just use `firstName` as in `query("firstName").is("John")`. You can
+even add methods like the previous section indicates like: `query("firstName.toLowerCase()").is("john")`.
+
+You may find it useful to add query to the Collection interface so that it is avaiable with every collection.
+
+```js
+var Backbone = require('backbone');
+var query = require('array-query'); // these lines may be skipped when using in the browser
+
+Backbone.Collection.prototype.query = function(field) {
+ return query.select(@models).where(field);
+}
+
+// then when using it remember to use `end()` as this is the `select()` style API.
+var activeUsers = userCollection.query('active').is(true).end();
+
+```
+
+One addition which was added specifically for Backbone (though could be altered to work elsewhere if needed) was the `set()` method.
+Using it you may set properties on all the matching objects.
+
+```js
+userCollection.query('selected').is(true).set({ selected: false }).end();
+```
+
+This will set all currently selected users to not selected. And because this is Backbone, any Views listening to the
+`change:selected` event can update accordingly.
View
353 node_modules/array-query/lib/query.js
@@ -0,0 +1,353 @@
+
+
+function query(field) {
+ if ( !(this instanceof query) ) {
+ return new query(field);
+ }
+
+ if (field) this._field(field);
+ this._eval = '';
+ this._lookups = {};
+ this._sorts = [];
+ this._actions = [];
+ this._map = [];
+ this._reduce = [];
+}
+
+function select(array) {
+ if ( !(this instanceof select) ) {
+ return new select(array);
+ }
+ query.call(this);
+ this.array = array;
+}
+
+query.select = select;
+
+(function() {
+
+ query.prototype = {
+ on: function(array) {
+ var filter = this.getFilter();
+ var sort = this.getSort();
+
+ if (filter) array = array.filter(filter, query);
+ else array = array.slice(); // copy array
+ if (sort) array.sort(sort);
+ if (this._offset) array = array.slice(this._offset, array.length - (this._limit || 0));
+ else if (this._limit) array.length = Math.min(array.length, this._limit);
+ this._actions.forEach(function(action) {
+ array.forEach(action);
+ });
+ this._map.forEach(function(action) {
+ array = array.map(action);
+ });
+ this._reduce.forEach(function(action) {
+ array = array.reduce(action);
+ });
+ return array;
+ },
+ getFilter: function() {
+ var queryStr = this.toString();
+ if (!queryStr) return null;
+ if (this._filterCache && this._filterCache.query == queryStr) return this._filterCache;
+ this._filterCache = new Function('obj', 'try { return ' + queryStr + '; } catch (e) { return false; }').bind(this);
+ this._filterCache.query = queryStr;
+ return this._filterCache;
+ },
+ getSort: function() {
+ if (!this._sorts.length) return null;
+ if (this._sortCache) return this._sortCache;
+ var sorts = this._sorts.map(function(sort) {
+ return sort.toFunction();
+ });
+ return this._sortCache = function(a, b) {
+ var direction = 0, i = 0, len = sorts.length;
+ while (i < len && direction == 0) {
+ direction = sorts[i++](a, b);
+ }
+ return direction;
+ };
+ },
+ getActions: function() {
+ return this._actions;
+ },
+ _field: function(field) {
+ this._expression = new Expression(field);
+ },
+ _add: function(eval, clean) {
+ this._eval += eval;
+ if (clean) this._expression = null;
+ return this;
+ },
+ _term: function(condition, field) {
+ this._flush();
+ this._eval += ' ' + condition + ' ';
+ if (typeof field == 'string') {
+ this._expression = new Expression(field);
+ } else if (field instanceof query) {
+ for (var i in field._lookups) {
+ if (field._lookups.hasOwnProperty(i)) this._lookups[i] = field._lookups[i];
+ }
+ this._eval += '(' + field + ')';
+ } else if (field) {
+ this._eval += '(' + field + ')';
+ }
+ return this;
+ },
+ _oper: function(operator, value) {
+ if (this._expression) {
+ this._expression.operator = operator;
+ this._expression.value = value;
+ }
+ return this;
+ },
+ _store: function(value) {
+ var postFix = Math.round(Math.random()*1000000);
+ this._lookups[postFix] = value;
+ return 'this._lookups[' + postFix + ']';
+ },
+ _sort: function(param, value) {
+ if (this._sorts.length == 0) return;
+ if (this._sortCache) delete this._sortCache;
+ var sort = this._sorts[this._sorts.length - 1];
+ if (typeof param == 'number') sort.direction = param;
+ else {
+ sort.type = sort[param];
+ if (value !== undefined) sort.direction = value;
+ }
+ return this;
+ },
+ _flush: function() {
+ if (this._expression) this._eval += this._expression;
+ this._expression = null;
+ },
+ toString: function() {
+ this._flush();
+ return this._eval;
+ },
+ and: function(field) {
+ return this._term('&&', field);
+ },
+ or: function(field) {
+ return this._term('||', field);
+ },
+ not: function(value) {
+ if (this._expression) {
+ this._expression.not = !this._expression.not;
+ if (value !== undefined) this._expression.value = value;
+ }
+ return this;
+ },
+ equals: function(value) {
+ return this._oper('===', value);
+ },
+ is: function(value) {
+ return this._oper('===', value);
+ },
+ isnt: function(value) {
+ this.not();
+ return this.is(value);
+ },
+ within: function(value) { // when an array of values is passed
+ var lookup = {};
+ for (var i in value) lookup[value[i]] = true;
+
+ if (this._expression) this._expression.template = '%not%operator[%term]';
+ return this._oper(this._store(lookup));
+ },
+ has: function(value) {
+ if (this._expression) this._expression.template = '%not(%term != null && %term.indexOf(%value) != -1)';
+ return this._oper(null, value);
+ },
+ startsWith: function(value) {
+ if (this._expression) this._expression.template = '%not(%term != null && %term.substr(0, %operator) == %value)';
+ return this._oper(value.length, value);
+ },
+ endsWith: function(value) {
+ if (this._expression) this._expression.template = '%not(%term != null && %term.substr(%term.length - %operator) == %value)';
+ return this._oper(value.length, value);
+ },
+ gt: function(value) {
+ return this._oper('>', value);
+ },
+ gte: function(value) {
+ return this._oper('>=', value);
+ },
+ lt: function(value) {
+ return this._oper('<', value);
+ },
+ lte: function(value) {
+ return this._oper('<=', value);
+ },
+ regex: function(value) {
+ if (this._expression) this._expression.template = '%not(%term != null && %operator.test(%term))';
+ return this._oper(this._store(value));
+ },
+ same: function(value) {
+ if (this._expression) this._expression.template = '%not(JSON.stringify(%term) %operator %value)';
+ value = JSON.stringify(value);
+ return this._oper('==', value);
+ },
+ filter: function(value) {
+ if (typeof value != 'function') throw new Error('query.filter() parameter must be a function');
+ if (this._expression) this._expression.template = '%not(%operator(%term))';
+ else {
+ this._expression = new Expression();
+ this._expression.template = '%not(%operator(obj))';
+ }
+ return this._oper(this._store(value));
+ },
+ search: function(words) {
+ words = RegExp.escape(words);
+ var exp = new RegExp('\\b' + words.split(/\s/).join('|\\b'));
+ return this.regex(exp);
+ },
+ type: function(value) {
+ if (typeof value == 'function') {
+ if (!this._expression) { // check the type of the main object
+ this._expression = new Expression();
+ this._expression.template = '%not(obj instanceof %operator)';
+ } else {
+ this._expression.template = '%not(%term instanceof %operator)';
+ }
+ return this._oper(this._store(value));
+ } else {
+ if (this._expression) this._expression.template = '%not(type(%term) %operator %value)';
+ return this._oper('==', value);
+ }
+ },
+ sort: function(field) {
+ if (this._sortCache) delete this._sortCache;
+ this._sorts.push(new Sort(field));
+ return this;
+ },
+ asc: function() {
+ return this._sort(1);
+ },
+ desc: function() {
+ return this._sort(-1);
+ },
+ regular: function() {
+ return this._sort('regular');
+ },
+ numeric: function() {
+ return this._sort('numeric');
+ },
+ date: function() {
+ return this._sort('date');
+ },
+ custom: function(value) {
+ return this._sort('custom', value);
+ },
+ limit: function(value) {
+ this._limit = value;
+ return this;
+ },
+ offset: function(value) {
+ this._offset = value;
+ return this;
+ },
+ set: function(attrs) {
+ this._actions.push(function(model) {
+ model.set(attrs);
+ });
+ return this;
+ }
+ };
+
+
+ select.prototype = Object.create(query.prototype);
+ select.prototype.where = function(field) {
+ if (field) this._expression = new Expression(field);
+ return this;
+ };
+ select.prototype.end = function() {
+ return this.on(this.array);
+ };
+
+
+
+
+ function Expression(term, operator, value, not) {
+ this.term = term;
+ this.operator = operator;
+ this.value = value;
+ this.not = not;
+ this.template = '%not(%term %operator %value)';
+ }
+
+ Expression.prototype = {
+ toString: function() {
+ var self = this;
+ return this.template.replace(/%\w+/g, function(match) {
+ switch(match) {
+ case '%not':
+ return self.not ? '!' : '';
+ case '%term':
+ return '(obj.' + self.term + ' || (typeof obj.get === "function" && obj.get("' + self.term + '")))' + (self.value instanceof Date ? '.getTime()' : '');
+ case '%operator':
+ return self.operator;
+ case '%value':
+ return JSON.stringify(self.value instanceof Date ? self.value.getTime() : self.value);
+ }
+ });
+ }
+ };
+
+
+ function Sort(term, type, direction) {
+ this.term = term;
+ this.type = type || this.regular;
+ this.direction = direction || 1;
+ }
+
+ Sort.prototype = {
+ toFunction: function() {
+ return this.type(this.term, this.direction);
+ },
+ regular: function(prop, order) {
+ return function(a, b) {
+ a = a[prop];
+ b = b[prop];
+ if (b == null) return -1;
+ if (a == null) return 1;
+ return order * (a > b ? 1 : (a < b ? -1 : 0));
+ };
+ },
+ numeric: function(prop, order) {
+ return function(a, b) {
+ a = parseFloat(a[prop]);
+ b = parseFloat(b[prop]);
+ if (b == null || isNaN(b)) return -1;
+ if (a == null || isNaN(a)) return 1;
+ return order * (a - b);
+ };
+ },
+ date: function(prop, order) {
+ return function(a, b) {
+ a = a[prop];
+ b = b[prop];
+ if (b == null) return -1;
+ if (a == null) return 1;
+ return order * (a.getTime() - b.getTime());
+ };
+ },
+ custom: function(prop, func) {
+ if (prop) {
+ return function(a, b) {
+ return func(a[prop], b[prop]);
+ };
+ } else {
+ return function(a, b) {
+ return func(a, b);
+ };
+ }
+ }
+ };
+
+})();
+
+if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
+ module.exports = query;
+}
View
42 node_modules/array-query/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "array-query",
+ "description": "Provides an interface to pull objects out of a JavaScript array with minimal code. Useful for Backbone collections and similar scenarios.",
+ "author": {
+ "name": "Jacob Wright",
+ "email": "jacwright@gmail.com"
+ },
+ "keywords": [
+ "array",
+ "database",
+ "query",
+ "select",
+ "where"
+ ],
+ "version": "0.1.0",
+ "main": "lib/query",
+ "maintainers": [
+ {
+ "name": "Jacob Wright",
+ "email": "jacwright@gmail.com",
+ "url": "http://jacwright.com"
+ }
+ ],
+ "contributors": [
+ {
+ "name": "Jacob Wright",
+ "email": "jacwright@gmail.com",
+ "url": "http://jacwright.com"
+ }
+ ],
+ "homepage": "https://github.com/jacwright/array-query",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/jacwright/array-query.git"
+ },
+ "readme": "query\n=====\n\nProvides an interface to pull objects out of a JavaScript array with minimal code. Useful for Backbone collections and similar scenarios. Can be used in the browser and on the server (node.js).\n\n## Why?\n\nThough you can do everything query provides you with the built-in Array methods (forEach, map, filter, etc), query can make it much more readable and concise.\n\n## Install\n\n```\nnpm install array-query\n```\n\nNow you can use it in your node.js project.\n\n```js\nvar query = require('array-query');\n\nvar firstJacob = query('name').startsWith('Jacob').on(users).pop();\n```\n\nOr just add the query.js file to your web page for use in your client-side JavaScript.\n\n## querying\n\nFinding objects in an array with query is easy. The API takes a property name first, then checks the value, then you can continue to add properties and checks until the `on` method is called with the array you are querying.\n\nIt is probably easier to see it in action. The following will return all users with the name Bob.\n\n```js\nvar allBobs = query('firstName').is('Bob').on(users);\n```\n\n### query Chaining\n\nWhen you call `query()` it creates a new query object which returns a reference to itself. In fact, most methods of the query object return a reference to itself enabling method chaining. For example, the following three queries are all the same.\n\n```js\nvar q1 = query(\"author\");\nq1.is(\"Terence Hanbury White\");\nq1.and(\"title\");\nq1.is(\"The Once and Future King\");\n\nvar q2 = query(\"author\").is(\"Terence Hanbury White\");\nq2.and(\"title\").is(\"The Once and Future King\");\n\nvar q3 = query(\"author\").is(\"Terence Hanbury White\").and(\"title\").is(\"The Once and Future King\");\n\nif (q1.toString() == q2.toString() == q3.toString()) alert(\"They're all the same!\");\n```\n\nMore examples:\n\n```js\nvar whiteBooks = query(\"author\").is(\"Terence Hanbury White\").on(books);\n\nvar theBooks = query(\"title\").startsWith(\"The\").on(books);\n\nvar bigBooks = query(\"pages\").gt(500).on(books);\n\nvar topTenBiggestBooks = query().sort(\"pages\").numeric().desc().limit(10).on(books);\n```\n\n### Select\n\nIf you'd rather start with the Array you may use the slightly different select().\n\n```js\nvar aBooks = select(books).where(\"title\").startsWith(\"A\").end();\n```\n\nThe two differences between `query` and `select` is that:\n\n 1. `query` ends with the array (e.g. `.on(books)`) and `select` starts with it (e.g. `select(books)`)\n 2. `select` needs to know when to be done chaining and to return the results, so it ends with `end()`\n\n## Basics\n\n### query\n\n`query` is the start of our query and may optionally take the first field we want to filter by. The `query` method does not need to take a field if you only want to sort or limit the objects.\n\n```js\nquery().sort(\"lastName\").limit(20).on(users);\n\nquery(\"age\").gt(20).on(users);\n```\n\nThe first query listed here shows using `query()` without a parameter. It sorts by lastName and limits the results to 20 objects. The second query gets all the objects where age is greater than 20. \n\n### and, or\n\nquery provides the ability to use `and()` and `or()` in putting together your query. These usually take a parameter, which can either be a field name or another query object. The field name is only the beginning of an expression and when used should be followed up with another method call such as `equals()`, `gt()`, etc.\n\n```js\nquery(\"username\").equals(\"test\").or(\"password\").equals(\"test\").on(users);\n```\n\nThis looks up all objects whose username or password is \"test\".\n\nQuery objects may be used inside the methods `and()` and `or()` to provide subqueries. This is like putting parenthesis around the expression.\n\n```js\nvar notMiddleAged = query(\"firstName\").equals(\"John\").and(query(\"age\").lt(20).or(\"age\").gt(60)));\n```\n\nThis query allows us to find all objects where the `firstName` is John and the age is either less than 20 or more than 60. We are unable to do this kind of sub-querying with the object-based API.\n\n## not\n\nThe `not()` method can be used in an expression to negate the results.\n\n```js\nquery(\"age\").not().gt(20).and(\"eyeColor\").not().equals(\"blue\").on(users);\n```\n\nThis query will get every object where age is not more than 20 and eye color is not blue.\n\n## Operations\n\n\n### is, equals\n\n`equals` is the most basic. The query should just be the value you want to match.\n\n```js\nquery(\"firstName\").is(\"John\").on(users);\n\nquery(\"lastName\").equals(\"Smith\").on(users);\n```\n\nThis will match all objects where property `firstName` equals \"John\". `is` and `equals` are synonymous.\n\n### within\n\n`within` tests whether the object's value is within a provided array of values.\n\n```js\nquery(\"firstName\").within([ \"John\", \"Jacob\", \"Jingle\", \"Heimer\" ]).on(users);\n```\n\nThis will match all objects whose firstName is \"John\", \"Jacob\", \"Jingle\", or \"Heimer\".\n\n### has\n\n`has` matches objects which have the provided value in an array.\n\n```js\ndb.add({ colors: [ \"red\", \"yellow\", \"blue\" ] });\n\nquery(\"colors\").has(\"red\").on(users);\n```\n\nThis will match the previously added object since it's colors array *has* the value \"red\". Note that if on the stored objects, colors is null or an empty array, it will not match since it doesn't have \"red\" in the colors array.\n\n### hasAll\n\n`hasAll` matches objects which have all the provided values in an array.\n\n```js\nquery(\"colors\").hasAll([\"red, \"blue\"]).on(users);\n```\n\nThis will match all objects which have both \"red\" and \"blue\" in their colors array.\n\n### startsWith\n\n`startsWith` matches the beginning of a value.\n\n```js\nquery(\"firstName\").startsWith(\"J\").on(users);\n```\n\nThis will match all objects whose `firstName` begins with \"J\".\n\n### endsWith\n\n`endsWith` matches the end of a value.\n\n```js\nquery(\"lastName\").endsWith(\"son\").on(users);\n```\n\nThis will match all objects whose `lastName` ends with \"son\".\n\n### gt\n\n`gt` matches objects whose value is greater than what's provided. Dates are supported.\n\n```js\nquery(\"age\").gt(20).on(users);\n```\n\nThis will match objects with `age` greater than 20;\n\n### gte\n\n`gte` matches objects whose value is greater than or equal to what's provided. Dates are supported.\n\n```js\nquery(\"age\").gte(20).on(users);\n```\n\nThis will match objects with `age` greater than or equal to 20;\n\n### lt\n\n`lt` matches objects whose value is less than what's provided. Dates are supported.\n\n```js\nquery(\"age\").lt(20).on(users);\n```\n\nThis will match objects with `age` less than 20;\n\n### lte\n\n`lte` matches objects whose value is less than or equal to what's provided. Dates are supported.\n\n```js\nquery(\"age\").lte(20).on(users);\n```\n\nThis will match objects with `age` less than or equal to 20;\n\n### regex\n\n`regex` matches objects whose values match the provided regular expression.\n\n```js\nquery(\"name\").regex(/[^\\w\\s]/).on(users);\n```\n\nThis will match objects that have a non word-or-space character in the `name` property.\n\n### same\n\n`same` matches objects where the value is the same when serialized into JSON. This allows arrays or objects to be matched without a reference to the original.\n\n```js\nusers.push({ name: { first: \"John\", last: \"Smith\" }, age: 30 });\n\nquery(\"name\").same({ first: \"John\", last: \"Smith\" }).on(users);\n```\n\nThis will match the added object since the `name` value is the same even if it isn't the exact instance in memory. Note that this uses the serialized JSON representation of both objects to compare. Dates should work with this method but hasn't been tested cross-browser.\n\n### type\n\n`type` matches objects where the object or property is of a given type. Valid types are a string of: object, array, number, boolean, null, undefined. Or an instance of a class (e.g. Date). If no property name is passed into the `query()`, `and()`, or `or()` methods then the type will match against the object itself rather than a property.\n\n```js\nquery(\"age\").type(\"number\").on(users);\n\nquery(\"published\").type(Date).on(users);\n\nquery(\"pet\").type(Dog).on(users);\n\nquery().type(User).or().type(Person).on(users); // matches if the object is and instance of User or Person (or a subclass thereof)\n```\n\nThe first call will match all objects with a number for the age. The second call will match all objects where published is an instance of Date. The third call will match all `User` and `Person` objects in the database.\n\n### filter\n\n`filter` allows a custom filter function to be run against the value of a property or the object as a whole. If the function returns true, the object is added to the query results.\n\n```js\nquery(\"firstName\").filter(function(name) {\n return name.toLowerCase().charAt(0) === \"a\";\n}).on(users);\n\nquery().filter(function(obj) {\n if (obj instanceof User) {\n return obj.active;\n } else if (obj instanceof Person) {\n return obj.trustLevel === \"trusted\";\n } else {\n return false;\n }\n}).on(users);\n```\n\nThe first query here uses a custom function to match against the value of the `firstName` property of every object. The second query uses a custom function to use custom logic to match against every object because no property name was passed into the `query()` function.\n\n### search\n\n`search` matches all objects with a full-text search on the given field.\n\n```js\nquery('bio').search(\"looking for all of these words\").on(users);\n```\n\nThis will match any objects which have the provided words in their `bio` field.\n\n### sort\n\nSorts the returned results by property. Additional sort methods may follow a sort to define it further: `asc()`, `desc()`, `regular()`, `numeric()`, `date()`, and `custom()`. The default sort uses `asc()` and `regular()`, so these don't need to be used explicitly. Custom allows sorting on a property or on the object as a whole.\n\n```js\nquery(\"active\").is(true).sort(\"lastName\").on(users);\n\nquery().sort(\"lastName\").desc().on(users);\n\nquery().sort(\"publishedDate\").date().desc().sort(\"title\").on(users);\n\nquery().sort(\"age\").custom(function(age1, age2) {\n if (age1 < age2) return -1;\n else if (age1 > age2) return 1;\n else return 0;\n}).on(users);\n\nquery().sort().custom(function(obj1, obj2) {\n return obj1.age - obj2.age;\n}).on(users);\n```\n\nThe first query sorts by `lastName` after selecting only active objects. The second query sorts all objects by `lastName` in descending or reverse order. The third query sorts by `publishDate` with most recent first, then by title for dates that are the same. The fourth query uses a custom sort on the age property. The last query uses a custom sort on the object as a whole.\n\n### limit\n\nLimit the results returned.\n\n```js\nquery().limit(10).on(users);\n```\n\nThis returns 10 objects from the top of the array.\n\n```js\nquery().sort('noisy').limit(10).on(users);\n```\n\nThis returns 10 noisiest users (whatever that might mean).\n\n### offset\n\nWorks with limit to select an offset which to start your limit at. This is used mostly for pagination.\n\n```js\nquery().limit(10).offset(100).on(users);\n```\n\nThis returns 10 objects starting at the 100th object.\n\n\n### Complex properties/fields\n\nQuery fields can be dot-delimited to match sub-properties. They may even use methods. Note that the query object does not check to ensure whether the property is null, so if it is on some objects but not others you'll want to check for that first.\n\n```js\nusers.push({ name: \"Bob\", colors: [ \"red\", \"yellow\", \"blue\" ] });\n\nquery(\"colors.length\").is(3).on(users);\nquery(\"colors.length\").gt(2).on(users);\nquery(\"colors.length\").lte(3).on(users);\n```\n\nThese will *all* match the added object because the length is equal to three, greater than two, and less than or equal to three.\n\n```js\nusers.push({ colors: [ \"red\", \"yellow\", \"blue\" ] });\nusers.push({ colors: null });\n\nquery(\"colors.length\").is(0).on(users); // will not match the newly added object because it is null\n\nquery(\"colors\").is(null).or(\"colors.length\").is(0).on(users); // this is how you should check\n```\n\nTo check for a name without respect to case you might do the following.\n\n```js\nquery('firstName.toLowerCase()').is('bob');\n\nquery().sort('lastName.toLowerCase()');\n```\n\nThe second query above will sort by last name irrespective of casing.\n\n## Backbone Support and Adding query to Backbone.Collections\n\nquery was built with Backbone in mind. Though you may use `query(\"get('firstName')\").is(\"John\")` to effectively work with\nBackbone models, query allows you to shorten that to just use `firstName` as in `query(\"firstName\").is(\"John\")`. You can\neven add methods like the previous section indicates like: `query(\"firstName.toLowerCase()\").is(\"john\")`.\n\nYou may find it useful to add query to the Collection interface so that it is avaiable with every collection.\n\n```js\nvar Backbone = require('backbone');\nvar query = require('array-query'); // these lines may be skipped when using in the browser\n\nBackbone.Collection.prototype.query = function(field) {\n return query.select(@models).where(field);\n}\n\n// then when using it remember to use `end()` as this is the `select()` style API.\nvar activeUsers = userCollection.query('active').is(true).end();\n\n```\n\nOne addition which was added specifically for Backbone (though could be altered to work elsewhere if needed) was the `set()` method.\nUsing it you may set properties on all the matching objects.\n\n```js\nuserCollection.query('selected').is(true).set({ selected: false }).end();\n```\n\nThis will set all currently selected users to not selected. And because this is Backbone, any Views listening to the\n`change:selected` event can update accordingly.\n",
+ "_id": "array-query@0.1.0",
+ "dist": {
+ "shasum": "dd4e1714de77fa1a4353ad739c8176161edd6c40"
+ },
+ "_from": "array-query"
+}
View
295 routes/batchimport.js
@@ -1,16 +1,30 @@
var sys = require('sys'),
- http = require('http'),
- url = require('url'),
- fs = require('fs'),
- path = require('path'),
- io = require('socket.io').listen(app),
- uuid = require('node-uuid'),
- redisUtil = require('../common/redisutil');
+ http = require('http'),
+ url = require('url'),
+ fs = require('fs'),
+ path = require('path'),
+ io = require('socket.io').listen(app),
+ uuid = require('node-uuid'),
+ query = require('array-query'),
+ redisUtil = require('../common/redisutil');
+
+var Business = require('../models/business'),
+ BusinessTaxReturn = require('../models/businesstaxreturn'),
+ Employee = require('../models/employee'),
+ FinancialYear = require('../models/financialyear'),
+ Individual = require('../models/individual'),
+ IndividualRelation = require('../models/individualrelation'),
+ IndividualStock = require('../models/individualstock'),
+ IndividualTaxReturn = require('../models/individualtaxreturn'),
+ Partner = require('../models/partner'),
+ Referral = require('../models/referral'),
+ Relation = require('../models/relation'),
+ Stock = require('../models/stock');
//TODO: Re-factor this to support multiple requests :)
var thisSocket;
io.sockets.on('connection', function(socket) {
- thisSocket = socket;
+ thisSocket = socket;
});
var FILE_DROP = 'temp';
@@ -18,136 +32,163 @@ var FILE_EXTENSION = '.zip';
// GET /batchimport
exports.importCSVData = function(request, response, next) {
- importCSVData(function(error) {
- if (error) return next(error);
- });
+ importCSVData(function(error) {
+ if (error) return next(error);
+ });
- response.render('batchimportstatus');
+ response.render('batchimportstatus');
};
function importCSVData(callback) {
- var zipFile = downloadZipFile(function(error, zipFile) {
- unzipCSVFilesIn(zipFile, function(error, csvFilesPath) {
- importCSVFilesIn(csvFilesPath, function(error) {
- //TODO: create relationships
- });
- });
- });
+ downloadZipFile(function(error, zipFile) {
+ unzipCSVFilesIn(zipFile, function(error, csvFilesPath) {
+ importCSVFilesIn(csvFilesPath, function(error) {
+ createNodeRelationships(function(error) {
+ callback(null);
+ });
+ });
+ });
+ });
}
function downloadZipFile(callback) {
- var downloadFileUrlString = "https://s3.amazonaws.com/myobadcodingcompetition/CSV+data.zip";
- var downloadFileUrl = url.parse(downloadFileUrlString);
- var host = downloadFileUrl.hostname;
- var downloadFilename = downloadFileUrl.pathname.split("/").pop();
-
- var downloadProgress = 0;
- var downloadFilePath = path.join(FILE_DROP, uuid.v1() + FILE_EXTENSION);
- var downloadFile = fs.createWriteStream(downloadFilePath, {
- 'flags': 'a'
- });
-
- var downloadOptions = {
- host: host,
- port: 80,
- path: downloadFileUrl.pathname,
- method: 'GET'
- };
-
- var request = http.request(downloadOptions, function(response) {
- sys.puts('Downloading file: ' + downloadFilename);
- sys.puts('STATUS: ' + response.statusCode);
- sys.puts('HEADERS: ' + JSON.stringify(response.headers));
-
- var contentLength = response.headers['content-length'];
-
- response.setEncoding('binary');
- response.on('data', function(chunk) {
- downloadProgress += chunk.length;
- downloadFile.write(chunk, encoding = 'binary');
-
- sys.puts('Download progress: ' + downloadProgress + ' bytes');
- if (thisSocket) {
- var progressPercentage = (downloadProgress / contentLength) * 100;
- thisSocket.emit('downloadZipFileProgress', {
- progress: progressPercentage
- });
- }
- });
- response.on('end', function() {
- downloadFile.end();
- sys.puts('Finished downloading ' + downloadFilename);
- callback(null, downloadFilePath);
- });
- });
-
- request.on('error', function(e) {
- sys.puts('Problem with request: ' + e.message);
- });
-
- request.end();
+ var downloadFileUrlString = "https://s3.amazonaws.com/myobadcodingcompetition/CSV+data.zip";
+ var downloadFileUrl = url.parse(downloadFileUrlString);
+ var host = downloadFileUrl.hostname;
+ var downloadFilename = downloadFileUrl.pathname.split("/").pop();
+
+ var downloadProgress = 0;
+ var downloadFilePath = path.join(FILE_DROP, uuid.v1() + FILE_EXTENSION);
+ var downloadFile = fs.createWriteStream(downloadFilePath, {
+ 'flags': 'a'
+ });
+
+ var downloadOptions = {
+ host: host,
+ port: 80,
+ path: downloadFileUrl.pathname,
+ method: 'GET'
+ };
+
+ var request = http.request(downloadOptions, function(response) {
+ sys.puts('Downloading file: ' + downloadFilename);
+ sys.puts('STATUS: ' + response.statusCode);
+ sys.puts('HEADERS: ' + JSON.stringify(response.headers));
+
+ var contentLength = response.headers['content-length'];
+
+ response.setEncoding('binary');
+ response.on('data', function(chunk) {
+ downloadProgress += chunk.length;
+ downloadFile.write(chunk, encoding = 'binary');
+
+ sys.puts('Download progress: ' + downloadProgress + ' bytes');
+ if (thisSocket) {
+ var progressPercentage = (downloadProgress / contentLength) * 100;
+ thisSocket.emit('downloadZipFileProgress', {
+ progress: progressPercentage
+ });
+ }
+ });
+ response.on('end', function() {
+ downloadFile.end();
+ sys.puts('Finished downloading ' + downloadFilename);
+ callback(null, downloadFilePath);
+ });
+ });
+
+ request.on('error', function(e) {
+ sys.puts('Problem with request: ' + e.message);
+ });
+
+ request.end();
}
function unzipCSVFilesIn(zipFilePath, callback) {
- sys.puts('Unzipping file: ' + zipFilePath);
- if (thisSocket) thisSocket.emit('unzipFileProgress', {
- progress: 'Unzipping file...'
- });
-
- var zipFileName = path.basename(zipFilePath, '.zip');
- var csvFilesPath = path.join(FILE_DROP, zipFileName);
-
- var spawn = require('child_process').spawn;
- var unzip = spawn('unzip', [zipFilePath, '-d', csvFilesPath]);
-
- unzip.stdout.on('data', function(data) {});
- unzip.stderr.on('data', function(data) {});
- unzip.on('exit', function(code) {
- sys.puts('Finished unzipping ' + zipFilePath);
- if (thisSocket) thisSocket.emit('unzipFileProgress', {
- progress: 'Finished unzipping file'
- });
-
- callback(null, csvFilesPath);
- });
+ sys.puts('Unzipping file: ' + zipFilePath);
+ if (thisSocket) thisSocket.emit('unzipFileProgress', {
+ progress: 'Unzipping file...'
+ });
+
+ var zipFileName = path.basename(zipFilePath, '.zip');
+ var csvFilesPath = path.join(FILE_DROP, zipFileName);
+
+ var spawn = require('child_process').spawn;
+ var unzip = spawn('unzip', [zipFilePath, '-d', csvFilesPath]);
+
+ unzip.stdout.on('data', function(data) {});
+ unzip.stderr.on('data', function(data) {});
+ unzip.on('exit', function(code) {
+ sys.puts('Finished unzipping ' + zipFilePath);
+ if (thisSocket) thisSocket.emit('unzipFileProgress', {
+ progress: 'Finished unzipping file'
+ });
+
+ callback(null, csvFilesPath);
+ });
}
function importCSVFilesIn(csvFilesPath, callback) {
- var Business = require('../models/business'),
- BusinessTaxReturn = require('../models/businesstaxreturn'),
- Employee = require('../models/employee'),
- FinancialYear = require('../models/financialyear'),
- Individual = require('../models/individual'),
- IndividualRelation = require('../models/individualrelation'),
- IndividualStock = require('../models/individualstock'),
- IndividualTaxReturn = require('../models/individualtaxreturn'),
- Partner = require('../models/partner'),
- Referral = require('../models/referral'),
- Relation = require('../models/relation'),
- Stock = require('../models/stock');
-
- Business.loadFromCSV(path.join(csvFilesPath, 'Business.csv'));
- BusinessTaxReturn.loadFromCSV(path.join(csvFilesPath, 'Business_TaxRecord.csv'));
- Employee.loadFromCSV(path.join(csvFilesPath, 'Employee.csv'));
- FinancialYear.loadFromCSV(path.join(csvFilesPath, 'FinancialYear.csv'));
- Individual.loadFromCSV(path.join(csvFilesPath, 'Individual.csv'));
- IndividualRelation.loadFromCSV(path.join(csvFilesPath, 'Individual_Relation.csv'));
- IndividualStock.loadFromCSV(path.join(csvFilesPath, 'Individual_Stock.csv'));
- IndividualTaxReturn.loadFromCSV(path.join(csvFilesPath, 'Individual_TaxRecord.csv'));
- Partner.loadFromCSV(path.join(csvFilesPath, 'Partner.csv'));
- Referral.loadFromCSV(path.join(csvFilesPath, 'Referral.csv'));
- Relation.loadFromCSV(path.join(csvFilesPath, 'Relation.csv'));
- Stock.loadFromCSV(path.join(csvFilesPath, 'Stock.csv'));
-
- setInterval(function() {
- redisUtil.getTotalNodes(function(err, totalNodes) {
- if (err) return callback(err);
-
- if (thisSocket) thisSocket.emit('importCSVProgress', {
- progress: totalNodes
- });
-
- if (!totalNodes) callback(null);
- });
- }, 1000);
+ sys.puts('Importing CSV files');
+ redisUtil.resetTotalNodes();
+
+ Business.loadFromCSV(path.join(csvFilesPath, 'Business.csv'));
+ BusinessTaxReturn.loadFromCSV(path.join(csvFilesPath, 'Business_TaxRecord.csv'));
+ Employee.loadFromCSV(path.join(csvFilesPath, 'Employee.csv'));
+ FinancialYear.loadFromCSV(path.join(csvFilesPath, 'FinancialYear.csv'));
+ Individual.loadFromCSV(path.join(csvFilesPath, 'Individual.csv'));
+ IndividualRelation.loadFromCSV(path.join(csvFilesPath, 'Individual_Relation.csv'));
+ IndividualStock.loadFromCSV(path.join(csvFilesPath, 'Individual_Stock.csv'));
+ IndividualTaxReturn.loadFromCSV(path.join(csvFilesPath, 'Individual_TaxRecord.csv'));
+ Partner.loadFromCSV(path.join(csvFilesPath, 'Partner.csv'));
+ Referral.loadFromCSV(path.join(csvFilesPath, 'Referral.csv'));
+ Relation.loadFromCSV(path.join(csvFilesPath, 'Relation.csv'));
+ Stock.loadFromCSV(path.join(csvFilesPath, 'Stock.csv'));
+
+ var refreshId = setInterval(function() {
+ redisUtil.getTotalNodes(function(err, totalNodes) {
+ if (err) return callback(err);
+
+ if (thisSocket) thisSocket.emit('importCSVProgress', {
+ progress: totalNodes
+ });
+
+ sys.puts('Total node: ' + totalNodes);
+
+ if (totalNodes == 0) {
+ sys.puts('Finished importing CSV files');
+ clearInterval(refreshId);
+ callback(null);
+ }
+ });
+ }, 1000);
+}
+
+function createNodeRelationships(callback) {
+ sys.puts('Creating node relationships')
+
+ createIndividualRelationships();
+}
+
+function createIndividualRelationships() {
+ Relation.getAll(function(error, relations) {
+ IndividualRelation.getAll(function(error, individualRelations) {
+ for (var i = 0; i < individualRelations.length; i++) {
+ var individualRelation = individualRelations[i];
+ var relationId = individualRelation.RelatedAs;
+ var personId1 = individualRelation.PersonId1;
+ var personId2 = individualRelation.PersonId2;
+ var relation = getRelation(relationId, relations);
+
+ Individual.relateTwoIndividuals(personId1, personId2, relation.Type, function(error, relationship) {
+ sys.puts('Relationship -> ' + JSON.stringify(relationship));
+ });
+ }
+ });
+ });
+}
+
+function getRelation(id, relations) {
+ var relationNode = query('Id').is(id).limit(1).on(relations)[0];
+ return new Relation(relationNode._node);
}
Please sign in to comment.
Something went wrong with that request. Please try again.