Skip to content

Commit

Permalink
Add path resolution to get helper
Browse files Browse the repository at this point in the history
refs TryGhost#5993

- deps: [email protected]
- adds `resolvePaths` method
- supports handlebars style arrays with `.[]`
- supports shorthand post.tags and post.author for common usecases
- adds more tests & improves existing ones
  • Loading branch information
ErisDS committed Oct 23, 2015
1 parent 64d9ce4 commit 994a20c
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 52 deletions.
40 changes: 40 additions & 0 deletions core/server/helpers/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ var _ = require('lodash'),
Promise = require('bluebird'),
errors = require('../errors'),
api = require('../api'),
jsonpath = require('jsonpath'),
resources,
pathAliases,
get;

// Endpoints that the helper is able to access
resources = ['posts', 'tags', 'users'];

// Short forms of paths which we should understand
pathAliases = {
'post.tags': 'post.tags[*].slug',
'post.author': 'post.author.slug'
};

/**
* ## Is Browse
* Is this a Browse request or a Read request?
Expand All @@ -29,6 +37,34 @@ function isBrowse(context, options) {
return browse;
}

/**
* ## Resolve Paths
* Find and resolve path strings
*
* @param {Object} data
* @param {String} value
* @returns {String}
*/
function resolvePaths(data, value) {
var regex = /\{\{(.*?)\}\}/g;

value = value.replace(regex, function (match, path) {
var result;

// Handle aliases
path = pathAliases[path] ? pathAliases[path] : path;
// Handle Handlebars .[] style arrays
path = path.replace(/\.\[/g, '[');

// Do the query, and convert from array to string
result = jsonpath.query(data, path).join(',');

return result;
});

return value;
}

/**
* ## Parse Options
* Ensure options passed in make sense
Expand All @@ -46,6 +82,10 @@ function parseOptions(data, options) {
options.author = options.author.slug;
}

if (_.isString(options.filter)) {
options.filter = resolvePaths(data, options.filter);
}

return options;
}

Expand Down
151 changes: 99 additions & 52 deletions core/test/unit/server_helpers/get_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ var should = require('should'),
// Stuff we are testing
handlebars = hbs.handlebars,
helpers = require('../../../server/helpers'),
api = require('../../../server/api');
api = require('../../../server/api'),

sandbox = sinon.sandbox.create();

describe('{{#get}} helper', function () {
var sandbox;
var fn, inverse;

before(function () {
utils.loadHelpers();
});

beforeEach(function () {
sandbox = sinon.sandbox.create();
fn = sandbox.spy();
inverse = sandbox.spy();
});

afterEach(function () {
Expand All @@ -31,18 +34,19 @@ describe('{{#get}} helper', function () {
});

describe('posts', function () {
var testPostsArr = [
{id: 1, title: 'Test Post 1', author: 'cameron'},
{id: 2, title: 'Test Post 2', author: 'cameron', featured: true},
var browseStub, readStub, testPostsArr = [
{id: 1, title: 'Test Post 1', author: {slug: 'cameron'}},
{id: 2, title: 'Test Post 2', author: {slug: 'cameron'}, featured: true},
{id: 3, title: 'Test Post 3', tags: [{slug: 'test'}]},
{id: 4, title: 'Test Post 4'}
];
],
meta = {pagination: {}};
beforeEach(function () {
var browseStub = sandbox.stub(api.posts, 'browse'),
readStub = sandbox.stub(api.posts, 'read');
browseStub = sandbox.stub(api.posts, 'browse');
readStub = sandbox.stub(api.posts, 'read');

browseStub.returns(new Promise.resolve({posts: testPostsArr}));
browseStub.withArgs({limit: '3'}).returns(new Promise.resolve({posts: testPostsArr.slice(0, 3)}));
browseStub.withArgs({limit: '3'}).returns(new Promise.resolve({posts: testPostsArr.slice(0, 3), meta: meta}));
browseStub.withArgs({limit: '1'}).returns(new Promise.resolve({posts: testPostsArr.slice(0, 1)}));
browseStub.withArgs({tag: 'test'}).returns(new Promise.resolve({posts: testPostsArr.slice(2, 3)}));
browseStub.withArgs({tag: 'none'}).returns(new Promise.resolve({posts: []}));
Expand All @@ -52,9 +56,6 @@ describe('{{#get}} helper', function () {
});

it('should handle default browse posts call', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -71,9 +72,6 @@ describe('{{#get}} helper', function () {
});

it('should handle browse posts call with limit 3', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -90,9 +88,6 @@ describe('{{#get}} helper', function () {
});

it('should handle browse posts call with limit 1', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -109,9 +104,6 @@ describe('{{#get}} helper', function () {
});

it('should handle browse posts call with limit 1', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -128,9 +120,6 @@ describe('{{#get}} helper', function () {
});

it('should handle browse post call with explicit tag', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -146,9 +135,6 @@ describe('{{#get}} helper', function () {
});

it('should handle browse post call with relative tag', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -164,9 +150,6 @@ describe('{{#get}} helper', function () {
});

it('should handle browse post call with explicit author', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -182,9 +165,6 @@ describe('{{#get}} helper', function () {
});

it('should handle browse post call with relative author', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -200,9 +180,6 @@ describe('{{#get}} helper', function () {
});

it('should handle browse post call with featured:true', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -218,9 +195,6 @@ describe('{{#get}} helper', function () {
});

it('should handle read post by id call', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -237,9 +211,6 @@ describe('{{#get}} helper', function () {
});

it('should handle empty result set', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -257,9 +228,6 @@ describe('{{#get}} helper', function () {

describe('general error handling', function () {
it('should return an error for an unknown resource', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'magic',
Expand All @@ -276,9 +244,6 @@ describe('{{#get}} helper', function () {
});

it('should handle error from the API', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts',
Expand All @@ -295,9 +260,6 @@ describe('{{#get}} helper', function () {
});

it('should show warning for call without any options', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy();

helpers.get.call(
{},
'posts'
Expand All @@ -309,4 +271,89 @@ describe('{{#get}} helper', function () {
}).catch(done);
});
});

describe('path resolution', function () {
var browseStub, readStub, data = {
post: {id: 3, title: 'Test 3', author: {slug: 'cameron'}, tags: [{slug: 'test'}, {slug: 'magic'}]}
};

beforeEach(function () {
browseStub = sandbox.stub(api.posts, 'browse').returns(new Promise.resolve());
readStub = sandbox.stub(api.posts, 'read').returns(new Promise.resolve());
});

it('should resolve post.tags alias', function (done) {
helpers.get.call(
data,
'posts',
{hash: {filter: 'tags:[{{post.tags}}]'}, fn: fn, inverse: inverse}
).then(function () {
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
browseStub.firstCall.args[0].filter.should.eql('tags:[test,magic]');

done();
}).catch(done);
});

it('should resolve post.author alias', function (done) {
helpers.get.call(
data,
'posts',
{hash: {filter: 'author:{{post.author}}'}, fn: fn, inverse: inverse}
).then(function () {
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
browseStub.firstCall.args[0].filter.should.eql('author:cameron');

done();
}).catch(done);
});

it('should resolve basic path', function (done) {
helpers.get.call(
data,
'posts',
{hash: {filter: 'id:-{{post.id}}'}, fn: fn, inverse: inverse}
).then(function () {
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
browseStub.firstCall.args[0].filter.should.eql('id:-3');

done();
}).catch(done);
});

it('should handle arrays the same as handlebars', function (done) {
var tpl = handlebars.compile('{{post.tags.[0].slug}}'),
output = tpl(data);

helpers.get.call(
data,
'posts',
{hash: {filter: 'tags:{{post.tags.[0].slug}}'}, fn: fn, inverse: inverse}
).then(function () {
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
browseStub.firstCall.args[0].filter.should.eql('tags:' + output);

done();
}).catch(done);
});

it('should output nothing if path does not resolve', function (done) {
helpers.get.call(
data,
'posts',
{hash: {filter: 'id:{{post.thing}}'}, fn: fn, inverse: inverse}
).then(function () {
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
browseStub.firstCall.args[0].filter.should.eql('id:');

done();
}).catch(done);
});
});
});

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"html-to-text": "1.3.2",
"intl": "1.0.0",
"intl-messageformat": "1.1.0",
"jsonpath": "0.2.0",
"knex": "0.7.3",
"lodash": "3.10.1",
"moment": "2.10.6",
Expand Down

0 comments on commit 994a20c

Please sign in to comment.