Skip to content

Commit

Permalink
Adding Object.size and moving isEmpty to enumerables in Array package
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewplummer committed Jul 20, 2012
1 parent fe544b4 commit 467492d
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 72 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -39,6 +39,8 @@ v1.3
- String#capitalize all will capitalize any letter after a letter that could not be capitalized.
- String#insert now treats negative indexes like String#slice
- Fixed issue with decodeBase64 shim (Issue 145)
- Added Object.size (also available to extended objects)
- Refactored Object.isEmpty to be an enumerable method in the Array package. This means that it will error on non-objects now.


v1.2.5
Expand Down
83 changes: 48 additions & 35 deletions lib/array.js
Expand Up @@ -1103,6 +1103,38 @@
*
***/

/***
* @method [enumerable](<obj>)
* @returns Boolean
* @short Enumerable methods on Arrays are also available to the Object class. They will perform their normal operations for every property available in <obj>.
* @extra In cases where a callback is used, instead of %element, index%, the callback will instead be passed %key, value%. Enumerable methods are also available to extended objects as instance methods. Note that an error will be raised when any enumerable method is call on a non-object.
*
* @set
* map
* any
* all
* none
* count
* find
* findAll
* reduce
* isEmpty
* sum
* average
* min
* max
* least
* most
*
* @example
*
* Object.any({foo:'bar'}, 'bar') -> true
* Object.extended({foo:'bar'}).any('bar') -> true
* Object.isEmpty({}) -> true
* Object.map({ fred: { age: 52 } }, 'age'); -> { fred: 52 }
*
***/

function buildEnumerableMethods(names, mapping) {
extendSimilar(object, false, false, names, function(methods, name) {
methods[name] = function(obj, arg1, arg2) {
Expand Down Expand Up @@ -1130,58 +1162,39 @@

extend(object, false, false, {

/***
* @method Object.map(<obj>, <map>)
* @returns Object
* @short Builds another object that contains values that are the result of calling <map> on each member.
* @extra <map> can also be a string, which is a shortcut for a function that gets that property (or invokes a function) on each element.
*
* @example
*
* Object.map({ num: 3 }, function(key, value) {
* return value * 3;
* });
* Object.map({ fred: { age: 52 } }, 'age');
*
***/
'map': function(obj, map) {
return object.keys(obj).reduce(function(result, key) {
result[key] = transformArgument(obj[key], map, obj, [key, obj[key], obj]);
return result;
}, {});
},

/***
* @method Object.reduce(<obj>, <fn>, [init])
* @returns Object
* @short Reduces the object to a single result based on its members (values).
* @extra If [init] is passed as a starting value, that value will be passed as the first argument to the callback. The second argument will be a member of the object (order is arbitrary). The result of the callback will then be used as the first argument of the next iteration. This is often refered to as "accumulation", and [init] is often called an "accumulator". If [init] is not passed, then <fn> will be called n - 1 times, where n is the size of the object. In this case, on the first iteration only, the first argument will be one member of the object, and the second will be another. After that callbacks work as normal, using the result of the previous callback as the first argument of the next.
*
*
*
* @example
*
* Object.reduce({ a:3, b:5 }, function(a, b) {
* return a * b;
* });
* Object.reduce({ a:3, b:5 }, function(a, b) {
* return a * b;
* }, 100);
*
***/
'reduce': function(obj) {
var values = object.keys(obj).map(function(key) {
return obj[key];
});
return values.reduce.apply(values, multiArgs(arguments).slice(1));
},

/***
* @method size(<obj>)
* @returns Number
* @short Returns the number of properties in <obj>.
* @extra %size% is available as an instance method on extended objects.
* @example
*
* Object.size({ foo: 'bar' }) -> 1
*
***/
'size': function (obj) {
return object.keys(obj).length;
}

});


buildEnhancements();
buildAlphanumericSort();
buildEnumerableMethods('any,all,none,count,find,findAll');
buildEnumerableMethods('any,all,none,count,find,findAll,isEmpty');
buildEnumerableMethods('sum,average,min,max,least,most', true);
buildObjectInstanceMethods('map,reduce', Hash);
buildObjectInstanceMethods('map,reduce,size', Hash);

2 changes: 0 additions & 2 deletions lib/date.js
@@ -1,8 +1,6 @@

// TODO final rinse (add comments and rearrange core methods)
// TODO Object.size??
// TODO Include DateRanges in the default package??
// TODO Other things that can be removed from default package?

/***
* Date package
Expand Down
19 changes: 1 addition & 18 deletions lib/object.js
Expand Up @@ -10,7 +10,7 @@
***/

var ObjectTypeMethods = 'isObject,isNaN'.split(',');
var ObjectHashMethods = 'keys,values,each,merge,isEmpty,clone,equal,watch,tap,has'.split(',');
var ObjectHashMethods = 'keys,values,each,merge,clone,equal,watch,tap,has'.split(',');

function setParamsObject(obj, param, value, deep) {
var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key;
Expand Down Expand Up @@ -283,23 +283,6 @@
return obj;
},

/***
* @method isEmpty(<obj>)
* @returns Boolean
* @short Returns true if <obj> is empty.
* @extra %isEmpty% is available as an instance method on extended objects.
* @example
*
* Object.isEmpty({}) -> true
* Object.isEmpty({foo:'bar'}) -> false
* Object.extended({foo:'bar'}).isEmpty() -> false
*
***/
'isEmpty': function(obj) {
if(!isObjectPrimitive(obj)) return !(obj && obj.length > 0);
return object.keys(obj).length == 0;
},

/***
* @method clone(<obj> = {}, [deep] = false)
* @returns Cloned object
Expand Down
50 changes: 50 additions & 0 deletions unit_tests/environments/sugar/array.js
Expand Up @@ -2535,5 +2535,55 @@ test('Array', function () {
testClassAndInstance('reduce', obj2, [function(acc, b) { return (acc.age ? acc.age : acc) + b.age; }], 110, 'Object.reduce | obj2 | a + b');
testClassAndInstance('reduce', obj2, [function(acc, b) { return acc - b.age; }, 10], -100, 'Object.reduce | obj2 | a - b with initial');


// Object.isEmpty

testClassAndInstance('isEmpty', {}, [], true, 'Object.isEmpty | object is empty');
testClassAndInstance('isEmpty', { broken: 'wear' }, [], false, 'Object.isEmpty | object is not empty');
testClassAndInstance('isEmpty', { length: 0 }, [], false, 'Object.isEmpty | simple object with length property is not empty');
testClassAndInstance('isEmpty', [], [], true, 'Object.isEmpty | empty array is empty');

raisesError(function(){ Object.isEmpty(null); }, 'Object.isEmpty | null is empty');
raisesError(function(){ Object.isEmpty(undefined); }, 'Object.isEmpty | undefined is empty');
raisesError(function(){ Object.isEmpty(''); }, 'Object.isEmpty | empty string is empty');
raisesError(function(){ Object.isEmpty('wasabi'); }, 'Object.isEmpty | non-empty string is not empty');
raisesError(function(){ Object.isEmpty(NaN); }, 'Object.isEmpty | NaN is empty');
raisesError(function(){ Object.isEmpty(8); }, 'Object.isEmpty | 8 is empty');

testClassAndInstance('isEmpty', (function(){ return this; }).call('wasabi'), [], false, 'Object.isEmpty | non-empty string is not empty');
testClassAndInstance('isEmpty', (function(){ return this; }).call(8), [], true, 'Object.isEmpty | 8 is empty');


// Object.size

testClassAndInstance('size', {}, [], 0, 'Object.size | empty object');
testClassAndInstance('size', {foo:'bar'}, [], 1, 'Object.size | 1 property');
testClassAndInstance('size', {foo:'bar',moo:'car'}, [], 2, 'Object.size | 2 properties');
testClassAndInstance('size', {foo:1}, [], 1, 'Object.size | numbers');
testClassAndInstance('size', {foo:/bar/}, [], 1, 'Object.size | regexes');
testClassAndInstance('size', {foo:function(){}}, [], 1, 'Object.size | functions');
testClassAndInstance('size', {foo:{bar:'car'}}, [], 1, 'Object.size | nested object');
testClassAndInstance('size', {foo:[1]}, [], 1, 'Object.size | nested array');
testClassAndInstance('size', ['a'], [], 1, 'Object.size | array');
testClassAndInstance('size', ['a','b'], [], 2, 'Object.size | array 2 elements');
testClassAndInstance('size', ['a','b','c'], [], 3, 'Object.size | array 3 elements');

raisesError(function(){ Object.size('foo') }, 'Object.size | string primitive');
raisesError(function(){ Object.size(1) }, 'Object.size | number primitive');
raisesError(function(){ Object.size(true) }, 'Object.size | boolean primitive');
raisesError(function(){ Object.size(null) }, 'Object.size | null');
raisesError(function(){ Object.size(undefined) }, 'Object.size | undefined');

testClassAndInstance('size', (function(){ return this; }).call('ho'), [], 2, 'Object.size | string object');
testClassAndInstance('size', (function(){ return this; }).call(3), [], 0, 'Object.size | number object');
testClassAndInstance('size', (function(){ return this; }).call(true), [], 0, 'Object.size | boolean object');

var Foo = function(){};
testClassAndInstance('size', new Foo, [], 0, 'Object.size | class instances');

var Foo = function(a){ this.a = a; };
testClassAndInstance('size', new Foo, [], 1, 'Object.size | class instances with a single property');


});

18 changes: 1 addition & 17 deletions unit_tests/environments/sugar/object.js
Expand Up @@ -452,22 +452,6 @@ test('Object', function () {



equal(Object.isEmpty({}), true, 'Object.isEmpty | object is empty');
equal(Object.isEmpty({ broken: 'wear' }), false, 'Object.isEmpty | object is not empty');

equal(Object.isEmpty(null), true, 'Object.isEmpty | null is empty');
equal(Object.isEmpty(undefined), true, 'Object.isEmpty | undefined is empty');
equal(Object.isEmpty(''), true, 'Object.isEmpty | empty string is empty');
equal(Object.isEmpty('wasabi'), false, 'Object.isEmpty | non-empty string is not empty');
equal(Object.isEmpty({ length: 0 }), false, 'Object.isEmpty | simple object with length property is not empty');
equal(Object.isEmpty([]), true, 'Object.isEmpty | empty array is empty');
equal(Object.isEmpty(NaN), true, 'Object.isEmpty | NaN is empty');
equal(Object.isEmpty(0), true, 'Object.isEmpty | 0 is empty');
equal(Object.isEmpty(8), true, 'Object.isEmpty | 8 is empty');

equal(Object.extended({}).isEmpty({}), true, 'Object#isEmpty | object is empty');
equal(Object.extended({ broken: 'wear' }).isEmpty(), false, 'Object#empty | object is not empty');

equal(Object.equal({ broken: 'wear' }, { broken: 'wear' }), true, 'Object.equal | objects are equal');
equal(Object.equal({ broken: 'wear' }, { broken: 'jumpy' }), false, 'Object.equal | objects are not equal');
equal(Object.equal({}, {}), true, 'Object.equal | empty objects are equal');
Expand Down Expand Up @@ -508,7 +492,6 @@ test('Object', function () {

equal(count, 3, 'Object | Object.prototype should have correctly called all functions', { prototype: 2, mootools: 2 });

equal(({}).isEmpty(), true, 'Object#empty | Object.prototype');
equal(({ foo: 'bar' }).equals({ foo: 'bar' }), true, 'Object#equals | Object.prototype');
equal(({ foo: 'bar' }).merge({ moo: 'car' }), { foo: 'bar', moo: 'car' }, 'Object#merge | Object.prototype', { mootools: Object.clone({ foo: 'bar', moo: 'car' }) });

Expand Down Expand Up @@ -823,5 +806,6 @@ test('Object', function () {

equal(Object.merge({}, obj1, true).reg === obj1.reg, false, 'Object.merge | deep merging will clone regexes');


});

0 comments on commit 467492d

Please sign in to comment.