-
Notifications
You must be signed in to change notification settings - Fork 115
_.islike(obj, pattern) tests objects are like patterns #196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ examples/*.css | |
examples/*.html | ||
examples/public | ||
bower_components/* | ||
*.swp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
### comparison.islike | ||
|
||
This is a function to check things, and particularly complex objects, fit a certain pattern. It is useful when you want to check that an argument you have received has the properties you expect. | ||
|
||
**Signature:** `_.islike(object:Any, pattern:Any)` | ||
|
||
Returns `true` if the object is like the pattern. `false` otherwise. | ||
|
||
```javascript | ||
_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"]}, | ||
{name: "", age: 0, hobbies: [""]} | ||
) | ||
|
||
``` | ||
|
||
To specify that a value should be a string you can put an empty string in the pattern `""`. For a number use `0` and for an array use an empty array `[]`. Nested objects are recursively checked. | ||
|
||
* `""` - stands for a string | ||
* `0` - stands for a number | ||
* `false` - stands for a boolean | ||
* `[]` - stands for an array | ||
* `Function` - stands for a function | ||
|
||
If you specify a type in the pattern then the value will be tested using `instanceof`. If you want to verify a function value (for instance a callback) you need to pass the `Function` type, since a normal `function() {}` is indistinguishable from type in Javascript. A more complex example using these follows: | ||
|
||
```javascript | ||
_.islike(myArgument, { | ||
title: "", count: "", owner: OwnerModel, success: Function, error: Function | ||
}); | ||
``` | ||
|
||
An array value can also be type checked by passing an array of types in the pattern. For example | ||
|
||
* `_.islike([ 1, 2, 3, "hello" ], [ 0 ])` - returns false | ||
* `_.islike([ 1, 2, 3, "hello", function() {} ], [ 0, "" ])` - returns false | ||
* `_.islike([ 1, 2, 3, "hello" ], [ 0, "" ]}` - returns true | ||
* `_.islike([ 1, 2, 3, "hello", function() {} ], [ 0, "", Function ]}` - returns true | ||
|
||
`[""]` allows an array of only strings and `["",0]` allows strings and numbers. This check is done using `typeof` so objects and arrays will fall into the same category. |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
$(document).ready(function() { | ||
|
||
module("underscore.comparison.islike"); | ||
|
||
test("string islike string", function() { | ||
ok(_.islike("hello, world", "")); | ||
}); | ||
|
||
test("number islike number", function() { | ||
ok(_.islike(32.4, 0)); | ||
}); | ||
|
||
test("boolean islike boolean", function() { | ||
ok(_.islike(true, true)); | ||
}); | ||
|
||
test("string is not like number", function() { | ||
equal(_.islike("hello", 0), false); | ||
}); | ||
|
||
test("boolean is not like number", function() { | ||
equal(_.islike(false, 0), false); | ||
}); | ||
|
||
test("array is like array", function() { | ||
ok(_.islike([1,2,3], [])); | ||
}); | ||
|
||
test("number array is typed like array", function() { | ||
ok(_.islike([1,2,3], [0])); | ||
}); | ||
|
||
test("string array is typed like array", function() { | ||
ok(_.islike(["hello", "world"], [""])); | ||
}); | ||
|
||
test("string array is not typed like number array", function() { | ||
equal(_.islike(["hello", "world"], [0]), false); | ||
}); | ||
|
||
test("object is like object", function() { | ||
ok(_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"]}, | ||
{name: "", age: 0, hobbies: [""]} | ||
)); | ||
}); | ||
|
||
test("object is not like object", function() { | ||
equal(_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"]}, | ||
{name: "", age: 0, hometown: "", hobbies: [""]} | ||
), false); | ||
}); | ||
|
||
test("object is like type", function() { | ||
var Type = function(){}; | ||
|
||
ok(_.islike(new Type, Type)); | ||
}); | ||
|
||
test("function is like Function", function() { | ||
ok(_.islike(function(){}, Function)); | ||
}); | ||
|
||
test("function is not like function", function() { | ||
equal(_.islike(function(){}, function(){}), false); | ||
}); | ||
|
||
test("object with functions is like object", function() { | ||
ok(_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"], done: function() { console.log("done");} }, | ||
{name: "", age: 0, hobbies: [""], done: Function} | ||
)); | ||
}); | ||
|
||
test("object with functions is not like object", function() { | ||
equal(_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"], done: true}, | ||
{name: "", age: 0, hobbies: [""], done: Function} | ||
), false); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* Tests if an object is like another. This means objects should follow the same | ||
* structure and arrays should contain the same types. | ||
* | ||
* E.g. | ||
* | ||
* _.islike( | ||
* {name: "James", age: 10, hobbies: ["football", "computer games", "baking"]}, | ||
* {name: "", age: 0, hobbies: [""]} | ||
* ) | ||
*/ | ||
(function() { | ||
// Establish the root object, `window` in the browser, or `require` it on the server. | ||
if (typeof exports === 'object') { | ||
_ = module.exports = require('underscore'); | ||
} | ||
|
||
var islike = function(obj, pattern) { | ||
if (typeof pattern === "function") { | ||
return obj instanceof pattern; | ||
} | ||
|
||
if (typeof obj !== typeof pattern) return false; | ||
if (pattern instanceof Array && !(obj instanceof Array)) return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, I would suggest There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
var type = typeof pattern; | ||
|
||
if (type == "object") { | ||
if (pattern instanceof Array) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should use |
||
if (pattern.length > 0) { | ||
var oTypes = _.uniq(_.map(obj, fTypeof)); | ||
var pTypes = _.uniq(_.map(pattern, fTypeof)); | ||
if (_.difference(oTypes, pTypes).length) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is sufficent. You should go key by key and ensure each key matches (recursive like). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I have misexplained the intention. It is not the idea that every element in the tested array is matched by an element in the pattern array, only that the set of types in the tested array matches the set of types in the pattern array. For example This type of test might be a bit weird though? Not what the user expects. |
||
return false; | ||
} | ||
} | ||
} else { // object | ||
if (pattern.constructor === pattern.constructor.prototype.constructor) { | ||
// for 'simple' objects we enumerate | ||
for (var k in pattern) { | ||
if (!pattern.hasOwnProperty(k)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just do a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I can't find There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, the official name for On Fri, Jun 19, 2015 at 11:20 AM, Joe Bain [email protected] wrote:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like I'm not sure if I prefer the for loop, it's slightly longer but a bit clearer and maybe quicker too since we don't need to create a lot of little closures. Also, since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree that it's more clear as a for in loop; and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also theres a potential bug currently if the user passes an object like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm of the opinion that if people start overriding However, I take your point about the bug. We could also use Regarding clarity, it's a bit of an taste thing I feel. Does the underscore style prefer one or the other? I think the speed is probably negligible, but since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah right. I'll make some changes and update the pull request. Probably on the weekend at this point, it's almost 6pm. |
||
continue; | ||
} | ||
var p = pattern[k]; | ||
var o = obj[k]; | ||
if (!islike(o, p)) { | ||
return false; | ||
} | ||
} | ||
} else { | ||
// for 'types' we just check the inheritance chain | ||
if (!(obj instanceof pattern.constructor)) { | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
var fTypeof = function(o) { | ||
return typeof o; | ||
}; | ||
|
||
_.mixin({islike: islike}); | ||
}).call(this); | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, while I personally prefer 4-space indents, the present convention in Underscore and Contrib is 2-space indents, so that's something that needs fixing as well.