Skip to content

Commit

Permalink
Merge branch 'feature/Autofill_And_Mappers_Registry' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
emyann committed Oct 21, 2016
2 parents 676b345 + 4a3097d commit 547a9ea
Show file tree
Hide file tree
Showing 6 changed files with 3,777 additions and 40 deletions.
137 changes: 108 additions & 29 deletions README.md
@@ -1,4 +1,5 @@
# Morphism
# Morphism

[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url]
> Helps you to transform any object structure to another
Expand All @@ -7,20 +8,39 @@
- Twitter: [@renaudin_yann][twitter-account]
- [GitHub Issues](https://github.com/emyann/morphism/issues)

## Installation
## Getting started

Install `morphism` using npm.

```sh
$ npm install --save morphism
npm install --save morphism
```

Then require it into any module.

```js
const Morphism = require('morphism');
```

Or using ES6 import style

```js
import Morphism from 'morphism';
```

If you're using [browserify](http://browserify.org/), the `morphism` npm module
also works from the browser.


## What does it do?

Morphism uses a semantic configuration to go through the collection of graph objects you have to process. Then it extracts and computes the value from the specified path(s). Finally, it sets this value to the destination property from the schema.

### Usage
## Usage
Morphism is curried function that allows a partial application with a semantic configuration. You can use it in 2 ways:

#### As a Mapper factory
### As a Mapper factory
```js
const Morphism = require('morphism'); // (ES6 Import) import Morphism from 'morphism';

let mapping = { ... }
let collectionOfObjects = [ ... ]
Expand All @@ -32,7 +52,7 @@ myAwesomeMapper(collectionOfObjects);
myAwesomeMapper(anotherCollection);
```

#### As a Static instance
### As a Static instance
```js
const Morphism = require('morphism');

Expand Down Expand Up @@ -69,6 +89,7 @@ let data = [{


### Simple mapping

```js
let data = [ ... ];
let mapping = {
Expand All @@ -79,14 +100,16 @@ let mapping = {

let results = Morphism(mapping, data);

console.log(results[0]) ==>
{
pseudo: 'John',
lastName: 'Smith',
city: 'New York'
}
// results[0]: {
// pseudo: 'John',
// lastName: 'Smith',
// city: 'New York'
// }
```


### Value transformation

```js
let data = [ ... ];
let mapping = {
Expand All @@ -103,36 +126,92 @@ let mapping = {
let mapper = Morphism(mapping);
let results = mapper(data);

console.log(results[0]) ==>
{
pseudo: 'John',
lastName: 'Smith',
city: 'new york',// <== toLowerCase
nbContacts: 2 // <== computed from the object
}
// results[0]): {
// pseudo: 'John',
// lastName: 'Smith',
// city: 'new york',// <== toLowerCase
// nbContacts: 2 // <== computed from the object
// }
```


### Values Aggregation

```js
let data = [ ... ];

let mapping = {
let mapping = {
user: ['firstName','lastName'] // aggregate the values to an object
city: 'address.city'
};

let results = Morphism(mapping, data);

console.log(results[0]) ==>
{
user: {
'firstName': 'John',
'lastName': 'Smith'
},
city: 'New York'
}
// results[0]: {
// user: {
// 'firstName': 'John',
// 'lastName': 'Smith'
// },
// city: 'New York'
// }
```

### Mappers Registry

Morphism provides a powerful local registry where you can store your mappings' configuration by specifying a Type.
The transformation sequences are stored as a function in a WeakMap to speed the processing.

**Register a mapping configuration**
```js
class User {
constructor(firstName, lastName, phoneNumber){
this.firstName = firstName;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
}
}

let mapUser = Morphism.register(User, schema);

// Map using the registered type and the registry
Morphism.map(User, data)

// Or Map using the mapper reference
mapUser(data);
```

### API

#### Register

Register a mapper for a specific type. The schema is optional.

```js
Morphism.register(type, schema:{});
```

#### Map

Map a collection of objects to the specified type

```js
Morphism.map(type, data:[]);
```

#### Get or Set an existing mapper configuration

```js
Morphism.setMapper(type, schema:{});
```

#### Delete a registered mapper

```js
Morphism.deleteMapper(type, schema:{});
```



## License

MIT © [Yann Renaudin][twitter-account]
Expand Down
102 changes: 92 additions & 10 deletions lib/morphism.js
@@ -1,34 +1,116 @@
import * as _ from 'lodash';
export default function Morphism(schema, items) {

export default function Morphism(schema, items, type) {
let _schema = _.clone(schema);
let transformer = (items) => {
return _.chain(items)
.map((obj) => {
return _.mapValues(_schema, (predicate) => {
let transformedObject = _.mapValues(_schema, (predicate) => {
if (!_.isObject(predicate)) { // a predicate string path
return _.get(obj, predicate);
}
else if (_.isFunction(predicate)) { // a predicate function without a path
return predicate(obj);
}
else if(_.isArray(predicate)){ // a predicate array of string path => agregator
return _.reduce(predicate, (delta, path) =>{
else if (_.isArray(predicate)) { // a predicate array of string path => agregator
return _.reduce(predicate, (delta, path) => {
return _.set(delta, path, _.get(obj, path));
},{});
}, {});
}
else { // a predicate object with a path and a function
let delta = _.get(obj, predicate.path);
return predicate.fn(delta);
}
});
}).value();
return transformedObject;
});
};

if (items === undefined) {
if (items === undefined && type === undefined) {
return (items) => {
return transformer(items).value();
};
} else if (type) {
return (items) => {
return transformer(items);
return transformer(items).map((o) => { return _.assignIn(new type(), o); }).value(); // TODO: Refactor this in a chain of responsibility pattern
};
} else {
return transformer(items);
}
else {
return transformer(items).value();
}
}


function factory(type, schema, items) {
let typeFields = _.keys(new type());
let defaultSchema = _.zipObject(typeFields, typeFields);
let finalSchema = _.assign(defaultSchema, schema);

return Morphism(finalSchema, items, type);
}


_.memoize.Cache = WeakMap;
const _registry = _.memoize(factory);

class _Morphism {
/**
* Register a mapper for a specific type against morphism
*
* @param {any} type
* @param {any} schema
*/
static register(type, schema) {
if (!type && !schema) {
throw new Error('type paramater is required when register a mapping');
} else if (_Morphism.exists(type)) {
throw new Error(`A mapper for ${type.name} has already been registered`);
}
return _registry(type, schema); // Store the result of the executed function in a WeakMap cache object
}

static map(type, data) {
return _registry(type)(data);
}

static getMapper(type) {
return _registry.cache.get(type);
}

static get mappers() {
return _registry.cache;
}

static exists(type) {
return _registry.cache.has(type);
}

static setMapper(type, schema) {
if (!schema) {
throw new Error(`The schema must be an Object. Found ${schema}`);
} else if (!_Morphism.exists(type)) {
throw new Error(`The type ${type.name} is not registered. Register it using \`Mophism.register(${type.name}, schema)\``);
} else {
let fn = factory(type, schema);
_registry.cache.set(type, fn);
return _registry(type);
}

}

static deleteMapper(type) {
return _registry.cache.delete(type);
}
}

let api = {
register: _Morphism.register,
map: _Morphism.map,
getMapper: _Morphism.getMapper,
setMapper: _Morphism.setMapper,
deleteMapper: _Morphism.deleteMapper,
mappers: _Morphism.mappers
};

_.assignIn(Morphism, api);

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -32,6 +32,7 @@
"eslint-config-xo-space": "^0.14.0",
"eslint-plugin-babel": "^3.3.0",
"expect": "^1.20.2",
"faker": "^3.1.0",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.2",
"gulp-coveralls": "^0.1.0",
Expand Down
14 changes: 14 additions & 0 deletions test/models.mocks.js
@@ -0,0 +1,14 @@
export class User {
constructor(firstName, lastName, phoneNumber){
this.firstName = firstName;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
}
}

export class Track {
constructor(id, title){
this.id = id;
this.title = title;
}
}

0 comments on commit 547a9ea

Please sign in to comment.