Skip to content

Commit

Permalink
Implement clone method (#97)
Browse files Browse the repository at this point in the history
* Add clone method

* Update dist

* Add docs about cloning

* Move attribute setters/getters to the final so methods won't override them

* Update dist

* Bump minor
  • Loading branch information
talyssonoc authored Sep 14, 2019
1 parent aac5225 commit a58b63c
Show file tree
Hide file tree
Showing 9 changed files with 397 additions and 44 deletions.
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/dist
/README.md
/CHANGELOG.md
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.7.0 - 2019-09-14
Enhancements:
* Add method to clone structures

## 1.6.0 - 2019-08-27
Enhancements:
* Allow custom error class to static mode
Expand Down
46 changes: 40 additions & 6 deletions dist/structure.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ return /******/ (function(modules) { // webpackBootstrap
attributeDescriptorFor = _require2.attributeDescriptorFor,
attributesDescriptorFor = _require2.attributesDescriptorFor;

var Cloning = __webpack_require__(39);

var define = Object.defineProperty;

function attributesDecorator(schema) {
Expand All @@ -118,8 +120,6 @@ return /******/ (function(modules) { // webpackBootstrap
}
});

define(WrapperClass, 'buildStrict', StrictMode.buildStrictDescriptorFor(WrapperClass, schemaOptions));

if (WrapperClass[SCHEMA]) {
schema = Object.assign({}, WrapperClass[SCHEMA], schema);
}
Expand All @@ -138,14 +138,18 @@ return /******/ (function(modules) { // webpackBootstrap

define(WrapperClass.prototype, 'attributes', attributesDescriptorFor(schema));

Object.keys(schema).forEach(function (attr) {
define(WrapperClass.prototype, attr, attributeDescriptorFor(attr, schema));
});

define(WrapperClass.prototype, 'validate', Validation.descriptorFor(schema));

define(WrapperClass.prototype, 'toJSON', Serialization.descriptor);

define(WrapperClass, 'buildStrict', StrictMode.buildStrictDescriptorFor(WrapperClass, schemaOptions));

define(WrapperClass.prototype, 'clone', Cloning.buildCloneDescriptorFor(WrapperClass));

Object.keys(schema).forEach(function (attr) {
define(WrapperClass.prototype, attr, attributeDescriptorFor(attr, schema));
});

return WrapperClass;
};
}
Expand Down Expand Up @@ -1329,6 +1333,36 @@ return /******/ (function(modules) { // webpackBootstrap
return attributes;
}

/***/ },
/* 39 */
/***/ function(module, exports) {

"use strict";

exports.buildCloneDescriptorFor = function buildCloneDescriptorFor(StructureClass) {
return {
configurable: true,
value: function clone() {
var overwrites = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var strict = options.strict;


var newAttributes = Object.assign({}, this.attributes, overwrites);

var cloneInstance = void 0;

if (strict) {
cloneInstance = StructureClass.buildStrict(newAttributes);
} else {
cloneInstance = new StructureClass(newAttributes);
}

return cloneInstance;
}
};
};

/***/ }
/******/ ])
});
Expand Down
51 changes: 26 additions & 25 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# Table of contents

* [Introduction](../README.md)
* [Schema concept](schema-concept/README.md)
* [Shorthand and complete type descriptor](schema-concept/shorthand-and-complete-type-descriptor.md)
* [Circular reference](schema-concept/circular-references-and-dynamic-types.md)
* [Nullable attributes](schema-concept/nullable-attributes.md)
* [Coercion](coercion/README.md)
* [Primitive type coercion](coercion/primitive-type-coercion.md)
* [Arrays coercion](coercion/arrays-and-array-subclasses.md)
* [Generic coercion](coercion/generic-coercion.md)
* [Recursive coercion](coercion/recursive-coercion.md)
* [Observations](coercion/observations.md)
* [Validation](validation/README.md)
* [String validations](validation/string-validations.md)
* [Number validations](validation/number-validations.md)
* [Boolean validations](validation/boolean-validations.md)
* [Date validations](validation/date-validations.md)
* [Array validations](validation/array-validations.md)
* [Attribute reference](validation/attribute-reference.md)
* [Nested validations](validation/nested-validations.md)
* [Validate raw data](validation/validate-raw-data.md)
* [Strict mode](strict-mode.md)
* [Serialization](serialization.md)
* [Contributing](../contributing.md)
* [License](../license.md)
* [GitHub](https://github.com/talyssonoc/structure)
- [Introduction](../README.md)
- [Schema concept](schema-concept/README.md)
- [Shorthand and complete type descriptor](schema-concept/shorthand-and-complete-type-descriptor.md)
- [Circular reference](schema-concept/circular-references-and-dynamic-types.md)
- [Nullable attributes](schema-concept/nullable-attributes.md)
- [Coercion](coercion/README.md)
- [Primitive type coercion](coercion/primitive-type-coercion.md)
- [Arrays coercion](coercion/arrays-and-array-subclasses.md)
- [Generic coercion](coercion/generic-coercion.md)
- [Recursive coercion](coercion/recursive-coercion.md)
- [Observations](coercion/observations.md)
- [Validation](validation/README.md)
- [String validations](validation/string-validations.md)
- [Number validations](validation/number-validations.md)
- [Boolean validations](validation/boolean-validations.md)
- [Date validations](validation/date-validations.md)
- [Array validations](validation/array-validations.md)
- [Attribute reference](validation/attribute-reference.md)
- [Nested validations](validation/nested-validations.md)
- [Validate raw data](validation/validate-raw-data.md)
- [Strict mode](strict-mode.md)
- [Cloning an instance](cloning.md)
- [Serialization](serialization.md)
- [Contributing](../contributing.md)
- [License](../license.md)
- [GitHub](https://github.com/talyssonoc/structure)
78 changes: 78 additions & 0 deletions docs/cloning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Cloning an instance

Structure adds a method `#clone` in order to be able to create a **shallow** copy of an instance. This methods accepts an optional overwrite object that permits you to overwrite some attributes of the copy.

```js
const { attributes } = require('structure');

const User = attributes({
name: String,
})(class User {});

const user = new User({
name: 'Me',
});

const cloneUserWithNoOverwrite = user.clone(); // User { name: 'Me }

const cloneWithOverwrite = user.clone({ name: 'Myself' }); // User { name: 'Myself' }
```

If the structure has a nested structure inside of it, the `#clone` method **will not** clone it but just point the new instance to the old value of the nested attribute.

```js
const { attributes } = require('structure');

const Book = attributes({
name: String,
})(class Book {});

const User = attributes({
name: String,
favoriteBook: Book,
})(class User {});

const user = new User({
name: 'Me',
favoriteBook: new Book({ name: 'The Silmarillion' }),
});

const cloneUserWithNoOverwrite = user.clone();
cloneUserWithNoOverwrite.favoriteBook === user.favoriteBook; // true, it was not cloned

const cloneWithOverwrite = user.clone({
favoriteBook: { name: 'The Lord of the Rings' },
});
cloneWithOverwrite.favoriteBook === user.favoriteBook; // false, it was **replaced** with the new value
cloneWithOverwrite.favoriteBook; // Book { name: 'The Lord of the Rings' }
```

## Strict mode

When cloning an instance, you can clone it in [strict mode](strict-mode.md) as well, so if the resulting clone is invalid it throws an error. To do that, pass a second argument to the `#clone` method with the option `strict` as `true`.

```js
const { attributes } = require('structure');

const User = attributes({
name: {
type: String,
required: true,
},
age: Number,
})(class User {});

const user = new User({
name: 'Me',
});

const clonedUser = user.clone(
{ name: null },
{ strict: true } // strict mode option
);

// Error: Invalid Attributes
// details: [
// { message: '"name" is required', path: 'name' }
// ]
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "structure",
"version": "1.6.0",
"version": "1.7.0",
"description": "A simple schema/attributes library built on top of modern JavaScript",
"main": "src/index.js",
"browser": "dist/structure.js",
Expand Down
27 changes: 16 additions & 11 deletions src/attributes/decorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
attributeDescriptorFor,
attributesDescriptorFor,
} = require('./descriptors');
const Cloning = require('../cloning');

const define = Object.defineProperty;

Expand All @@ -29,11 +30,6 @@ function attributesDecorator(schema, schemaOptions = {}) {
},
});

define(WrapperClass, 'buildStrict', StrictMode.buildStrictDescriptorFor(
WrapperClass,
schemaOptions
));

if (WrapperClass[SCHEMA]) {
schema = Object.assign({}, WrapperClass[SCHEMA], schema);
}
Expand All @@ -54,19 +50,28 @@ function attributesDecorator(schema, schemaOptions = {}) {
schema
));

define(WrapperClass.prototype, 'validate', Validation.descriptorFor(
schema
));

define(WrapperClass.prototype, 'toJSON', Serialization.descriptor);

define(WrapperClass, 'buildStrict', StrictMode.buildStrictDescriptorFor(
WrapperClass,
schemaOptions
));

define(WrapperClass.prototype, 'clone', Cloning.buildCloneDescriptorFor(
WrapperClass
));

Object.keys(schema).forEach((attr) => {
define(WrapperClass.prototype, attr, attributeDescriptorFor(
attr,
schema
));
});

define(WrapperClass.prototype, 'validate', Validation.descriptorFor(
schema
));

define(WrapperClass.prototype, 'toJSON', Serialization.descriptor);

return WrapperClass;
};
}
Expand Down
22 changes: 22 additions & 0 deletions src/cloning/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
exports.buildCloneDescriptorFor = function buildCloneDescriptorFor(
StructureClass
) {
return {
configurable: true,
value: function clone(overwrites = {}, options = {}) {
const { strict } = options;

const newAttributes = Object.assign({}, this.attributes, overwrites);

let cloneInstance;

if (strict) {
cloneInstance = StructureClass.buildStrict(newAttributes);
} else {
cloneInstance = new StructureClass(newAttributes);
}

return cloneInstance;
},
};
};
Loading

0 comments on commit a58b63c

Please sign in to comment.