Skip to content
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

Nested properties #27

Merged
merged 4 commits into from
Mar 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Sifter is a client and server-side library (via [UMD](https://github.com/umdjs/u
* **Supports díåcritîçs.**<br>For example, if searching for "montana" and an item in the set has a value of "montaña", it will still be matched. Sorting will also play nicely with diacritics.
* **Smart scoring.**<br>Items are scored / sorted intelligently depending on where a match is found in the string (how close to the beginning) and what percentage of the string matches.
* **Multi-field sorting.**<br>When scores aren't enough to go by – like when getting results for an empty query – it can sort by one or more fields. For example, sort by a person's first name and last name without actually merging the properties to a single string.
* **Nested properties.**<br>Allows to search and sort on nested properties so you can perform search on complex objects without flattening them simply by using dot-notation to reference fields (ie. `nested.property`).

```sh
$ npm install sifter # node.js
Expand Down Expand Up @@ -112,6 +113,11 @@ Performs a search for `query` with the provided `options`.
<td valign="top">string</td>
<td valign="top">Determines how multiple search terms are joined ("and" or "or").</td>
</tr>
<tr>
<td valign="top">"nesting"</td>
<td valign="top">boolean</td>
<td valign="top">If <code>true</code>, nested fields will be available for search and sort using dot-notation to reference them.<br>ex:<code>nested.property</code><br><em>Warning: can reduce performances</em></td>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nitpick: "performances" should be "performance"

Nevermind, will just fix this up in master :)

</tr>
</table>

## CLI
Expand Down
24 changes: 20 additions & 4 deletions lib/sifter.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,14 @@
* @returns {function}
*/
Sifter.prototype.getScoreFunction = function(search, options) {
var self, fields, tokens, token_count;
var self, fields, tokens, token_count, nesting;

self = this;
search = self.prepareSearch(search, options);
tokens = search.tokens;
fields = search.options.fields;
token_count = tokens.length;
nesting = search.options.nesting;

/**
* Calculates how close of a match the
Expand Down Expand Up @@ -157,12 +158,12 @@
}
if (field_count === 1) {
return function(token, data) {
return scoreValue(data[fields[0]], token);
return scoreValue(getattr(data, fields[0], nesting), token);
};
}
return function(token, data) {
for (var i = 0, sum = 0; i < field_count; i++) {
sum += scoreValue(data[fields[i]], token);
sum += scoreValue(getattr(data, fields[i], nesting), token);
}
return sum / field_count;
};
Expand Down Expand Up @@ -223,7 +224,7 @@
*/
get_field = function(name, result) {
if (name === '$score') return result.score;
return self.items[result.id][name];
return getattr(self.items[result.id], name, options.nesting);
};

// parse options
Expand Down Expand Up @@ -412,6 +413,21 @@
return a;
};

/**
* A property getter resolving dot-notation
* @param {Object} obj The root object to fetch property on
* @param {String} name The optionally dotted property name to fetch
* @param {Boolean} nesting Handle nesting or not
* @return {Object} The resolved property value
*/
var getattr = function(obj, name, nesting) {
if (!obj || !name) return;
if (!nesting) return obj[name];
var names = name.split(".");
while(names.length && (obj = obj[names.shift()]));
return obj;
};

var trim = function(str) {
return (str + '').replace(/^\s+|\s+$|/g, '');
};
Expand Down
34 changes: 32 additions & 2 deletions test/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('Sifter', function() {

});

describe('#prepareSeach()', function() {
describe('#prepareSearch()', function() {

it('should normalize options', function() {
var sifter = new Sifter([{field: 'a'}, {}]);
Expand Down Expand Up @@ -170,6 +170,21 @@ describe('Sifter', function() {
assert.equal(result.items[0].id, 0);
});

it('should allow to search nested fields', function() {
var sifter = new Sifter([
{fields: {nested: 'aaa'}},
{fields: {nested: 'add'}},
{fields: {nested: 'abb'}}
]);
var result = sifter.search('aaa', {
fields: 'fields.nested',
nesting: true
});

assert.equal(result.items.length, 1);
assert.equal(result.items[0].id, 0);
});

describe('sorting', function() {
it('should respect "sort_empty" option when query absent', function() {
var sifter = new Sifter([
Expand Down Expand Up @@ -307,6 +322,21 @@ describe('Sifter', function() {
assert.equal(result.items[0].id, 1);
assert.equal(result.items[1].id, 0);
});
it('should work with nested fields', function() {
var sifter = new Sifter([
{fields: {nested: 'aaa'}},
{fields: {nested: 'add'}},
{fields: {nested: 'abb'}}
]);
var result = sifter.search('', {
fields: [],
sort: {field: 'fields.nested'},
nesting: true
});
assert.equal(result.items[0].id, 0);
assert.equal(result.items[1].id, 2);
assert.equal(result.items[2].id, 1);
});
});

describe('returned results', function() {
Expand Down Expand Up @@ -453,4 +483,4 @@ describe('Sifter', function() {
});

});
});
});