Skip to content

Commit

Permalink
Replaced underscore-deep-extend with a copy of deep-extend without 'B…
Browse files Browse the repository at this point in the history
…uffer' type usage.

Added deep-extend unit tests
  • Loading branch information
Nir Hadassi committed May 30, 2016
1 parent aa4d9d9 commit a0d69d2
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 16 deletions.
6 changes: 3 additions & 3 deletions lib/AnalyticsDispatcher.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var _ = require('lodash');
var deepExtend = require('./deep-extend');
var AnalyticsContext = require('./AnalyticsContext');

function AnalyticsDispatcher(dispatch, context){
Expand All @@ -12,8 +12,8 @@ function AnalyticsDispatcher(dispatch, context){

function unionContexts(oldContext, newContext){
var unionContext = {};
_.deepExtend(unionContext, oldContext);
_.deepExtend(unionContext, newContext || {});
deepExtend(unionContext, oldContext);
deepExtend(unionContext, newContext || {});
unionContext.Scopes = unionArrays(oldContext.Scopes,(newContext || {}).Scopes);
unionContext.Filters = unionArrays(oldContext.Filters,(newContext || {}).Filters);
return unionContext;
Expand Down
11 changes: 4 additions & 7 deletions lib/createRootDispatcher.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
var Promise = require('bluebird');
var underscoreDeepExtend = require('underscore-deep-extend');

var _ = require('lodash');
_.mixin({deepExtend: underscoreDeepExtend(_)});

var deepExtend = require('./deep-extend');
var AnalyticsContext = require('./AnalyticsContext');
var AnalyticsEventModel = require('./AnalyticsEventModel');
var AnalyticsDispatcher = require('./AnalyticsDispatcher');
Expand All @@ -12,9 +9,9 @@ var createEventModel = function(eventName, context){
var eventModel = new AnalyticsEventModel();
eventModel.Name = eventName;
eventModel.Scope = context.Scopes.join("_");
_.deepExtend(eventModel.ExtraData, context.ExtraData);
_.deepExtend(eventModel.MetaData, context.MetaData);
_.deepExtend(eventModel.Identities, context.Identities);
deepExtend(eventModel.ExtraData, context.ExtraData);
deepExtend(eventModel.MetaData, context.MetaData);
deepExtend(eventModel.Identities, context.Identities);
return context.Filters.reduce(function(cur, next) {
return cur.then(
function(){
Expand Down
139 changes: 139 additions & 0 deletions lib/deep-extend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*!
* @description Recursive object extending
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
* @license MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2013-2015 Viacheslav Lotsmanov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

'use strict';

function isSpecificValue(val) {
return !!(
val instanceof Date
|| val instanceof RegExp
);
}

function cloneSpecificValue(val) {
if (val instanceof Date) {
return new Date(val.getTime());
} else if (val instanceof RegExp) {
return new RegExp(val);
} else {
throw new Error('Unexpected situation');
}
}

/**
* Recursive cloning array.
*/
function deepCloneArray(arr) {
var clone = [];
arr.forEach(function (item, index) {
if (typeof item === 'object' && item !== null) {
if (Array.isArray(item)) {
clone[index] = deepCloneArray(item);
} else if (isSpecificValue(item)) {
clone[index] = cloneSpecificValue(item);
} else {
clone[index] = deepExtend({}, item);
}
} else {
clone[index] = item;
}
});
return clone;
}

/**
* Extening object that entered in first argument.
*
* Returns extended object or false if have no target object or incorrect type.
*
* If you wish to clone source object (without modify it), just use empty new
* object as first argument, like this:
* deepExtend({}, yourObj_1, [yourObj_N]);
*/
var deepExtend = module.exports = function (/*obj_1, [obj_2], [obj_N]*/) {
if (arguments.length < 1 || typeof arguments[0] !== 'object') {
return false;
}

if (arguments.length < 2) {
return arguments[0];
}

var target = arguments[0];

// convert arguments to array and cut off target object
var args = Array.prototype.slice.call(arguments, 1);

var val, src, clone;

args.forEach(function (obj) {
// skip argument if it is array or isn't object
if (typeof obj !== 'object' || Array.isArray(obj)) {
return;
}

Object.keys(obj).forEach(function (key) {
src = target[key]; // source value
val = obj[key]; // new value

// recursion prevention
if (val === target) {
return;

/**
* if new value isn't object then just overwrite by new value
* instead of extending.
*/
} else if (typeof val !== 'object' || val === null) {
target[key] = val;
return;

// just clone arrays (and recursive clone objects inside)
} else if (Array.isArray(val)) {
target[key] = deepCloneArray(val);
return;

// custom cloning and overwrite for specific objects
} else if (isSpecificValue(val)) {
target[key] = cloneSpecificValue(val);
return;

// overwrite by new value if source isn't object or array
} else if (typeof src !== 'object' || src === null || Array.isArray(src)) {
target[key] = deepExtend({}, val);
return;

// source value and new value is objects both, extending...
} else {
target[key] = deepExtend(src, val);
return;
}
});
});

return target;
}
4 changes: 2 additions & 2 deletions lib/writers/mixpanelWriter.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
var _ = require('lodash');
var deepExtend = require('../deep-extend');

module.exports = function(id){
return function(eventModel){
if (!mixpanel) return;

var extra = _.deepExtend({}, eventModel.Identities, eventModel.ExtraData, eventModel.MetaData);
var extra = deepExtend({}, eventModel.Identities, eventModel.ExtraData, eventModel.MetaData);
var previousDistinctId;
if (eventModel.Identities[id]){
mixpanel.identify(eventModel.Identities[id]);
Expand Down
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "shisell",
"version": "0.0.4",
"version": "0.0.5",
"description": "A service agnostic JS library for building immutable scoped analytic event dispatchers with extra data, identities and lazy filters.",
"main": "index.js",
"scripts": {
Expand All @@ -18,9 +18,7 @@
"author": "Soluto",
"license": "MIT",
"dependencies": {
"bluebird": "2.10.2",
"lodash": "^4.13.1",
"underscore-deep-extend": "^1.1.5"
"bluebird": "2.10.2"
},
"devDependencies": {
"chai": "^3.4.0",
Expand Down
142 changes: 142 additions & 0 deletions test/deepExtendSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
'use strict';
var chai = require("chai");
var sinonChai = require("sinon-chai");

chai.should();
chai.use(sinonChai);
var extend = require('../lib/deep-extend');

describe('deep-extend', function () {

it('can extend on 1 level', function () {
var a = { hello: 1 };
var b = { world: 2 };
extend(a, b);
a.should.eql({
hello: 1,
world: 2
});
});

it('can extend on 2 levels', function () {
var a = { person: { name: 'John' } };
var b = { person: { age: 30 } };
extend(a, b);
a.should.eql({
person: { name: 'John', age: 30 }
});
});

it('Date objects', function () {
var a = { d: new Date() };
var b = extend({}, a);
b.d.should.instanceOf(Date);
});

it('Date object is cloned', function () {
var a = { d: new Date() };
var b = extend({}, a);
b.d.setTime( (new Date()).getTime() + 100000 );
b.d.getTime().should.not.eql( a.d.getTime() );
});

it('RegExp objects', function () {
var a = { d: new RegExp() };
var b = extend({}, a);
b.d.should.instanceOf(RegExp);
});

it('RegExp object is cloned', function () {
var a = { d: new RegExp('b', 'g') };
var b = extend({}, a);
b.d.test('abc');
b.d.lastIndex.should.not.eql( a.d.lastIndex );
});

it('doesn\'t change sources', function () {
var a = {a: [1]};
var b = {a: [2]};
var c = {c: 3};
var d = extend({}, a, b, c);

a.should.eql({a: [1]});
b.should.eql({a: [2]});
c.should.eql({c: 3});
});

it('example from README.md', function () {
var obj1 = {
a: 1,
b: 2,
d: {
a: 1,
b: [],
c: { test1: 123, test2: 321 }
},
f: 5,
g: 123,
i: 321,
j: [1, 2]
};
var obj2 = {
b: 3,
c: 5,
d: {
b: { first: 'one', second: 'two' },
c: { test2: 222 }
},
e: { one: 1, two: 2 },
f: [],
g: (void 0),
h: /abc/g,
i: null,
j: [3, 4]
};

extend(obj1, obj2);

obj1.should.eql({
a: 1,
b: 3,
d: {
a: 1,
b: { first: 'one', second: 'two' },
c: { test1: 123, test2: 222 }
},
f: [],
g: undefined,
c: 5,
e: { one: 1, two: 2 },
h: /abc/g,
i: null,
j: [3, 4]
});

('g' in obj1).should.eql(true);
('x' in obj1).should.eql(false);
});

it('clone arrays instead of extend', function () {
extend({a: [1, 2, 3]}, {a: [2, 3]}).should.eql({a: [2, 3]});
});

it('checking keys for hasOwnPrototype', function () {
var A = function () {
this.x = 1;
this.y = 2;
};
A.prototype.z = 3;
var foo = new A();
extend({x: 123}, foo).should.eql({
x: 1,
y: 2
});
foo.z = 5;
extend({x: 123}, foo, {y: 22}).should.eql({
x: 1,
y: 22,
z: 5
});
});

});

0 comments on commit a0d69d2

Please sign in to comment.