Skip to content

Commit

Permalink
Merge 14fe94b into 5de73b7
Browse files Browse the repository at this point in the history
  • Loading branch information
JustinBeaudry committed Sep 1, 2018
2 parents 5de73b7 + 14fe94b commit e05a2d6
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 119 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Metamon
======

[![npm version](https://badge.fury.io/js/metamon.svg)](https://badge.fury.io/js/metamon) [![Build Status](https://travis-ci.org/JustinBeaudry/metamon.svg?branch=master)](https://travis-ci.org/JustinBeaudry/metamon) [![Coverage Status](https://coveralls.io/repos/github/JustinBeaudry/metamon/badge.svg?branch=master)](https://coveralls.io/github/JustinBeaudry/metamon?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/8573b69add63f7c41c66/maintainability)](https://codeclimate.com/github/JustinBeaudry/metamon/maintainability)
[![npm version](https://badge.fury.io/js/metamon.svg)](https://badge.fury.io/js/metamon) [![Build Status](https://travis-ci.org/JustinBeaudry/metamon.svg?branch=master)](https://travis-ci.org/JustinBeaudry/metamon) [![Coverage Status](https://coveralls.io/repos/github/JustinBeaudry/metamon/badge.svg?branch=master)](https://coveralls.io/github/JustinBeaudry/metamon?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/0d74c51f1c973bf7ff83/maintainability)](https://codeclimate.com/github/JustinBeaudry/metamon/maintainability)

![Ditto](https://media.giphy.com/media/uQZTgSuGZMTHG/giphy.gif)

Expand Down
18 changes: 16 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metamon",
"version": "2.6.2",
"version": "3.0.0-0",
"description": "model, and collection library for class based or pojo models",
"main": "index.js",
"browser": "dist/metamon.min.js",
Expand All @@ -17,7 +17,7 @@
"build-browser": "rm -rf dist && npx rollup -c",
"generate-docs": "rm -rf docs && npx jsdoc -c ./.jsdoc.json",
"serve-docs": "cd docs && python -m SimpleHTTPServer",
"prepublishOnly": "npm run build-prod; npm run generate-docs;npm test"
"prepublishOnly": "npm test;npm run build-prod; npm run generate-docs"
},
"keywords": [
"joi",
Expand All @@ -42,7 +42,7 @@
"enjoi-browser": "1.x.x",
"joi-browser": "13.x.x",
"lodash": "4.x.x",
"uuid": "3.x.x"
"rxjs": "^6.3.1"
},
"devDependencies": {
"ava": "^1.0.0-beta.7",
Expand Down
73 changes: 59 additions & 14 deletions src/Collection.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import _ from 'lodash';
import {throwError} from 'rxjs';
import {merge} from 'rxjs/operators';
import Serializable from './Serializable';
import Model from './Model';
import {MissingIndexError} from './errors';
import {toJSON, getType} from './utils';
import {MissingFieldError, MissingIndexError, MissingModelError, ModelExistsError, UnexpectedTypeError} from './errors';
import {toJSON, getType, onFieldsUpdate} from './utils';

/**
*
* @extends Serializable
Expand Down Expand Up @@ -33,15 +36,13 @@ class Collection extends Serializable {
* assert(pokemonCollection.get(name).quote, quote) // true
*
* @param {Object|Array} data - data to create a collection with on construction. data can be passed after instantiation
* @param {Model} model - the Model to be used when adding data to a collection. A Collection will force all data into this Model.
* @param {Model|Function} (model) - The Model to be used when adding data to a collection.
* A Collection will force all data into this Model if used.
* @param {String} (indexBy) [id] - the field that the collection should index on
* @throws {Error}
*/
constructor(data, model, indexBy='id') {
super();
if (!model || !(model.prototype instanceof Model)) {
throw new Error('A Collection requires a Model, and the Model must inherit from the Model Base Class.');
}
this.index = {};
this.indexBy = indexBy;
this.Model = model;
Expand Down Expand Up @@ -87,7 +88,7 @@ class Collection extends Serializable {
setFromObject.call(this, data);
break;
default:
throw new TypeError(`Expected data to be of type {Object|Array} and got ${typeof data}`);
throw new UnexpectedTypeError();
}
return this;
}
Expand All @@ -110,7 +111,7 @@ class Collection extends Serializable {
let convertedModel = convertToModel.call(this, model);
const key = convertedModel[this.indexBy];
if (this.index[key]) {
throw new Error('Model already exists in Collection index. Call update if you wish to update model');
throw new ModelExistsError();
}
this.index[key] = convertedModel;
return this;
Expand All @@ -131,7 +132,7 @@ class Collection extends Serializable {
remove(modelId) {
let model = this.get(modelId);
if (!model) {
throw new Error('Model does not exist in Collection index. Call add().');
throw new MissingModelError();
}
let convertedModel = convertToModel.call(this, model);
const key = convertedModel[this.indexBy];
Expand All @@ -158,10 +159,13 @@ class Collection extends Serializable {
update(model) {
let convertedModel = convertToModel.call(this, model);
const key = convertedModel[this.indexBy];
if (!this.index[key]) {
throw new Error('Model to update does not exist in Collection index');
if (!key || !this.get(key)) {
throw new MissingModelError();
}
this.index[key] = convertedModel;
// if we have a metamon Model instance, use `model.set()` so we can validate the schema
this.index[key] = (convertedModel instanceof Model)
? this.get(key).set(model)
: convertedModel;
return this;
}
/**
Expand Down Expand Up @@ -223,11 +227,49 @@ class Collection extends Serializable {
*
* converts index to an array and stringifies
*
* @param {Boolean} asIndex [false] - return the index as JSON
* @returns {String}
*/
toJSON() {
toJSON(asIndex=false) {
if (asIndex) {
return toJSON(this.index);
}
return toJSON(this.toArray());
}
onCollectionUpdate() {
return merge(
this.toArray().map(model => {
if (model instanceof Model) {
return model.onFieldsUpdate().pipe(
map(value => {
return {
[model[this.indexBy]]: value
}
})
)
}
return onFieldsUpdate
.call(model, Object.keys(model))
.pipe(map(value => {
return {
[model[this.indexBy]]: value
}
}))
})
);
}
onModelUpdate(id) {
const model = this.get(id);
if (!model) {
return throwError(new MissingModelError());
}
// if the model is an instance of model, just use the Model method
if (model instanceof Model) {
return model.onFieldsUpdate();
}
// if the model is a plain object just use onFieldsUpdate util
return onFieldsUpdate.call(model, Object.keys(model));
}
}
/**
*
Expand All @@ -246,9 +288,12 @@ function setFromObject(datum) {
*
* @ignore
* @param {Object|Model} model
* @returns {Model}
* @returns {Model|Object}
*/
function convertToModel(model) {
if (!this.Model) {
return model;
}
if (!(model instanceof this.Model)) {
model = new this.Model(model);
}
Expand Down
29 changes: 18 additions & 11 deletions src/ErrorFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@
* construct a custom error constructor and streamline
* the stack trace captured in the custom error
*
* @param {String|ErrorConstructor} (message)
* @param {ErrorConstructor} (ErrorClass)
* @param {String|Function<String>} message
* @returns {Error}
*
*/
function ErrorFactory(message, ErrorClass=Error) {
if (typeof message === 'function') {
ErrorClass = message;
}
return class CustomError extends ErrorClass {
function ErrorFactory(message) {
return class CustomError extends Error {
constructor(...args) {
let errorMessage;
// if a string is passed the string is the error message
if (typeof message === 'string') {
args.unshift(message);
errorMessage = message;
} else if (typeof message === 'function') {
// if a function is passed pass along the values sent to the error constructor
// to allow the construction of more expressive error messages
errorMessage = message(args);
} else {
// otherwise we just pass the first argument passed to the constructor as the error message
errorMessage = args && args[0];
}
super(...args);
if (typeof ErrorClass.captureStackTrace === 'function') {
ErrorClass.captureStackTrace(this, CustomError);
super(errorMessage);
// this ensures that we don't include the ErrorFactory constructor in the stack
// SEE: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, CustomError);
}
}
}
Expand Down
52 changes: 37 additions & 15 deletions src/Model.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {unix} from './utils';
import Serializable from './Serializable';
import uuid from 'uuid';
import _ from 'lodash';
import {pick, omit} from 'lodash';
import Enjoi from 'enjoi-browser/lib/enjoi';
import Serializable from './Serializable';
import {onFieldsUpdate, onFieldUpdate} from './utils';

const MODEL_METHODS = [
'set',
'addView',
'getView',
'onFieldUpdate',
'onFieldsUpdate'
];

const _defaults = Symbol('defaults');
const _schema = Symbol('schema');
Expand Down Expand Up @@ -80,9 +87,7 @@ class Model extends Serializable {
* @param {Object} data
* @param {Object} (options) [{
* defaults: {
* id: {String},
* modified: {Number}
* created: {Number}
* <String>: <*>
* },
* schema: {
* <String>: {
Expand All @@ -99,11 +104,7 @@ class Model extends Serializable {
* @throws {Error}
*/
constructor(data={}, options={
defaults: {
id: uuid.v4(),
modified: unix(),
created: unix()
},
defaults: null,
schema: null,
schemaOptions: null,
views: null
Expand Down Expand Up @@ -135,7 +136,7 @@ class Model extends Serializable {
}
});
}
this.set(data);
return this.set(data);
}
/**
*
Expand All @@ -161,6 +162,7 @@ class Model extends Serializable {
Object.assign(this, this[_defaults], data);
}
Object.assign(this, data);
return this;
}
/**
*
Expand All @@ -183,6 +185,7 @@ class Model extends Serializable {
whitelist: isWhitelist,
fields: fields
};
return this;
}
/**
*
Expand All @@ -197,10 +200,29 @@ class Model extends Serializable {
}
const view = this[_views][name];
if (view.whitelist) {
return _.pick(this, view.fields);
return pick(this, view.fields);
} else {
return _.omit(this, view.fields);
return omit(this, view.fields);
}
}
/**
*
* @param {String} field
* @returns {Observable<any>}
*/
onFieldUpdate(field) {
onFieldUpdate.call(this, field);
}
/**
*
* @param {Array<String>} (fields) - defaults to all fields on model
* @returns {Observable<any>}
*/
onFieldsUpdate(fields) {
if (!fields) {
fields = Object.keys(omit(this.toObject(), MODEL_METHODS));
}
onFieldsUpdate.call(this, fields);
}
}
export default Model;

0 comments on commit e05a2d6

Please sign in to comment.