|
1 | 1 | # JoinJS |
2 | 2 |
|
3 | | -A library to map complex database joins to nested objects. |
| 3 | +JoinJS is a JavaScript library to map complex database joins to nested objects. It is a simpler alternative that gives you direct control over your database interactions compared to a full-blown Object-Relation Mapper (ORM). |
| 4 | + |
| 5 | +## Motivation |
| 6 | +ORMs generally introduce a thick layer of abstraction between objects and the database which usually hinders rather than helps the productivity of the developer. In complex use cases, it is difficult enough to devise queries that will generate the desired results, but now you have to *teach* the ORM to generate the same query. For anyone who has worked with an ORM knows that that's extra time and you may not be able to produce the same query. In the worst case scenario, the ORM may hit the database multiple times for something that you were able to do in a single query. |
| 7 | + |
| 8 | +JoinJS takes a much simpler approach (inspired by a Java library called [MyBatis](http://mybatis.github.io/mybatis-3/)). You can use any database driver or query builder (such as [Kenx.js](http://knexjs.org/)) to query your database, however you use JoinJS to convert the returned result set in to a hierarchy of nested objects. |
| 9 | + |
| 10 | +## Example |
| 11 | +Suppose you have a one-to-many relationship between a `Team` and its `Player`s. You want to retrieve all teams along with their players. Here's the query for to do this: |
| 12 | + |
| 13 | +```sql |
| 14 | +SELECT t.id AS team_id, |
| 15 | + t.name AS team_name, |
| 16 | + p.id AS player_id, |
| 17 | + p.name AS player_name |
| 18 | +FROM teams t |
| 19 | + LEFT OUTER JOIN players p |
| 20 | + ON t.id = p.team_id; |
| 21 | +``` |
| 22 | + |
| 23 | +Assume that this query returns the following result set |
| 24 | + |
| 25 | +```javascript |
| 26 | +var resultSet = [ |
| 27 | + { team_id: 1, team_name: 'New England Patriots', player_id: 1, player_name: 'Tom Brady' }, |
| 28 | + { team_id: 1, team_name: 'New England Patriots', player_id: 2, player_name: 'Rob Gronkowski' }, |
| 29 | + { team_id: 2, team_name: 'New York Jets', player_id: 3, player_name: 'Geno Smith' }, |
| 30 | + { team_id: 2, team_name: 'New York Jets', player_id: 4, player_name: 'Darrelle Revis' } |
| 31 | +]; |
| 32 | + |
| 33 | +``` |
| 34 | + |
| 35 | +You can use JoinJS to convert this result set in to an array of teams with nested players: |
| 36 | + |
| 37 | +```javascript |
| 38 | +[ |
| 39 | + { |
| 40 | + id: 1, |
| 41 | + name: 'New England Patriots' |
| 42 | + players: [ |
| 43 | + { id: 1, name: 'Tom Brady' }, |
| 44 | + { id: 2, name: 'Rob Gronkowski' }, |
| 45 | + ] |
| 46 | + }, |
| 47 | + { |
| 48 | + id: 2, |
| 49 | + name: 'New York Jets' |
| 50 | + players: [ |
| 51 | + { id: 1, name: 'Tom Brady' }, |
| 52 | + { id: 2, name: 'Rob Gronkowski' }, |
| 53 | + ] |
| 54 | + } |
| 55 | +] |
| 56 | +``` |
| 57 | + |
| 58 | +To teach JoinJS how to do this, you must create result maps that describe your objects: |
| 59 | + |
| 60 | +```javascript |
| 61 | +var resultMaps = [ |
| 62 | + { |
| 63 | + mapId: 'teamMap', |
| 64 | + idProperty: {name: 'id', column: 'id'}, |
| 65 | + properties: [ |
| 66 | + {name: 'name', column: 'name'} |
| 67 | + ], |
| 68 | + collections: [ |
| 69 | + {name: 'players', mapId: 'playerMap', columnPrefix: 'player_'} |
| 70 | + ] |
| 71 | + }, |
| 72 | + { |
| 73 | + mapId: 'playerMap', |
| 74 | + idProperty: {name: 'id', column: 'id'}, |
| 75 | + properties: [ |
| 76 | + {name: 'name', column: 'name'} |
| 77 | + ] |
| 78 | + } |
| 79 | +] |
| 80 | +``` |
| 81 | + |
| 82 | +Once you have created these result maps, you can simply call JoinJS to map your result set in to objects: |
| 83 | + |
| 84 | +```javascript |
| 85 | +var mappedResult = joinjs.map(resultSet, resultMaps, 'teamMap', 'team_'); |
| 86 | +``` |
| 87 | + |
| 88 | +That's it! It doesn't matter how deep or complex your object hierarchy is, JoinJS can map it for you by supplying the appropriate result maps. Read the documentation below for more details. You can find more examples in the [test suite](https://github.com/archfirst/joinjs/tree/master/test). Check out the following project for an example of a real application built with JoinJS: |
| 89 | + |
| 90 | +- [Manage My Money](https://github.com/archfirst/manage-my-money-server) - An application to manage your personal finances |
| 91 | + |
| 92 | +## Installation |
| 93 | + |
| 94 | +```bash |
| 95 | +$ npm install join-js |
| 96 | +``` |
| 97 | + |
| 98 | +## Documentation |
| 99 | + |
| 100 | +### ResultMap |
| 101 | +ResulpMaps are used to teach JoinJS how to map result sets to objects. Each result map focuses on a single object. The properties of a ResultMap are described below. You can find many examples of result maps in the [test suite](https://github.com/archfirst/joinjs/tree/master/test). |
| 102 | + |
| 103 | +- `{String} mapId` - A unique identifier for the map |
| 104 | + |
| 105 | +- `{function} createNew` (optional) - A function that returns a blank new instance of the mapped object. Use these property to construct your custom objects instead of generic `Object`s. |
| 106 | + |
| 107 | +- `{Object} idProperty` (optional) - mapping of id property from result set to mapped object. Default is `{name: 'id', column: 'id'}`. |
| 108 | + - `name` - property that identifies the mapped object |
| 109 | + - `column` - property that identifies the database record in the result set |
| 110 | + |
| 111 | +- `{Array} properties` - mapping of other properties from result set to mapped object |
| 112 | + - `name` - property name in the mapped object |
| 113 | + - `column` - property name in the result set |
| 114 | + |
| 115 | +- `{Array} associations` - specifies references to other objects |
| 116 | + - `name` - property name of the association reference in the mapped object |
| 117 | + - `mapId` - identifier of the map for the associated object |
| 118 | + - `columnPrefix` (optional) - a column prefix to apply to every element of the associated object. Default is an empty string. |
| 119 | + |
| 120 | +- `{Array} collections` - specifies an array of references to other objects |
| 121 | + - `name` - property name of the array in the mapped object |
| 122 | + - `mapId` - identifier of the map for the associated objects |
| 123 | + - `columnPrefix` (optional) - a column prefix to apply to every element of the associated objects. Default is an empty string. |
| 124 | + |
| 125 | +### API |
| 126 | +JoinJS exposes two very simple functions that give you the power to map any result set to one of more JavaScript objects. |
| 127 | + |
| 128 | + |
| 129 | +#### map(resultSet, maps, mapId, columnPrefix) |
| 130 | + |
| 131 | +Maps a resultSet to a collection. |
| 132 | + |
| 133 | +- `{Array} resultSet` - an array of database results |
| 134 | +- `{Array} maps` - an array of result maps |
| 135 | +- `{String} mapId` - mapId of the top-level objects in the resultSet |
| 136 | +- `{String} columnPrefix` (optional) - prefix that should be applied to the column names of the top-level objects |
| 137 | + |
| 138 | +Returns a collection of mapped objects. |
| 139 | + |
| 140 | + |
| 141 | +#### mapOne(resultSet, maps, mapId, columnPrefix, isRequired) |
| 142 | + |
| 143 | +This is a convenience method that maps a resultSet to a single object. |
| 144 | + |
| 145 | +- `{Array} resultSet` - an array of database results |
| 146 | +- `{Array} maps` - an array of result maps |
| 147 | +- `{String} mapId` - mapId of the top-level objects in the resultSet |
| 148 | +- `{String} columnPrefix` (optional) - prefix that should be applied to the column names of the top-level objects |
| 149 | +- `{boolean} isRequired` (optional) - is a mapped object required to be returned? Default is true. |
| 150 | + |
| 151 | +Returns the mapped object or `null` if no object was mapped. |
| 152 | + |
| 153 | +Throws a NotFoundError if no object is mapped and isRequired is true. |
0 commit comments