Skip to content

Commit 7c656ce

Browse files
committed
Merge pull request #40 from pixelhandler/optional-type-for-relation
Add options argument for findOne/findMany Resource helpers
2 parents 06601bc + b352362 commit 7c656ce

File tree

4 files changed

+104
-12
lines changed

4 files changed

+104
-12
lines changed

addon/adapters/application.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,19 @@ export default Ember.Object.extend(Ember.Evented, {
9292
9393
@method findRelated
9494
@param {String} resource name (plural) to lookup the service object w/ serializer
95+
or {Object} `{'resource': relation, 'type': type}` used when type is not the
96+
same as the resource name, to fetch relationship using a different service
9597
@param {String} url
9698
@return {Promise}
9799
*/
98100
findRelated(resource, url) {
99-
const service = this.container.lookup('service:' + pluralize(resource));
101+
let type = resource;
102+
if (typeof type === 'object') {
103+
resource = resource.resource;
104+
type = resource.type;
105+
}
106+
let service = this.container.lookup('service:' + pluralize(type));
107+
url = this.fetchUrl(url);
100108
return service.fetch(url, { method: 'GET' });
101109
},
102110

addon/models/resource.js

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,15 @@ const RelatedProxyUtil = Ember.Object.extend({
380380
*/
381381
relationship: null,
382382

383+
/**
384+
The name of the type of resource
385+
386+
@property type
387+
@type String
388+
@required
389+
*/
390+
type: null,
391+
383392
/**
384393
Proxy for the requested relation, resolves w/ content from fulfilled promise
385394
@@ -389,11 +398,12 @@ const RelatedProxyUtil = Ember.Object.extend({
389398
@return {PromiseProxy} proxy
390399
*/
391400
createProxy: function (resource, proxyFactory) {
392-
const relation = this.get('relationship');
393-
const url = this.proxyUrl(resource, relation);
394-
const service = resource.container.lookup('service:' + pluralize(relation));
401+
let relation = this.get('relationship');
402+
let type = this.get('type');
403+
let url = this.proxyUrl(resource, relation);
404+
let service = resource.container.lookup('service:' + pluralize(type));
395405
let promise = this.promiseFromCache(resource, relation, service);
396-
promise = promise || service.findRelated(relation, url);
406+
promise = promise || service.findRelated({'resource': relation, 'type': type}, url);
397407
let proxy = proxyFactory.extend(Ember.PromiseProxyMixin, {
398408
'promise': promise, 'type': relation
399409
});
@@ -476,14 +486,30 @@ function linksPath(relation) {
476486
477487
@method hasOne
478488
@param {String} relation
489+
Or, {Object} with properties for `resource` and `type`
479490
*/
480491
export function hasOne(relation) {
481-
assertDasherizedHasOneRelation(relation);
482-
const util = RelatedProxyUtil.create({'relationship': relation});
483-
const path = linksPath(relation);
492+
let type = relation;
493+
if (typeof type === 'object') {
494+
assertResourceAndTypeProps(relation);
495+
type = relation.type;
496+
relation = relation.resource;
497+
}
498+
assertDasherizedHasOneRelation(type);
499+
let util = RelatedProxyUtil.create({'relationship': relation, 'type': type});
500+
let path = linksPath(relation);
484501
return Ember.computed(path, function () {
485502
return util.createProxy(this, Ember.ObjectProxy);
486-
}).meta({relation: relation, kind: 'hasOne'});
503+
}).meta({relation: relation, type: type, kind: 'hasOne'});
504+
}
505+
506+
function assertResourceAndTypeProps(relation) {
507+
try {
508+
let msg = 'Options must include properties: resource, type';
509+
Ember.assert(msg, relation && relation.resource && relation.type);
510+
} catch(e) {
511+
console.warn(e.message);
512+
}
487513
}
488514

489515
function assertDasherizedHasOneRelation(name) {
@@ -502,14 +528,21 @@ function assertDasherizedHasOneRelation(name) {
502528
503529
@method hasMany
504530
@param {String} relation
531+
Or, {Object} with properties for `resource` and `type`
505532
*/
506533
export function hasMany(relation) {
534+
let type = relation;
535+
if (typeof type === 'object') {
536+
assertResourceAndTypeProps(relation);
537+
type = relation.type;
538+
relation = relation.resource;
539+
}
507540
assertDasherizedHasManyRelation(relation);
508-
const util = RelatedProxyUtil.create({'relationship': relation});
509-
const path = linksPath(relation);
541+
let util = RelatedProxyUtil.create({'relationship': relation, 'type': type});
542+
let path = linksPath(relation);
510543
return Ember.computed(path, function () {
511544
return util.createProxy(this, Ember.ArrayProxy);
512-
}).meta({relation: relation, kind: 'hasMany'});
545+
}).meta({relation: relation, type: type, kind: 'hasMany'});
513546
}
514547

515548
function assertDasherizedHasManyRelation(name) {

tests/helpers/resources.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ export const Commenter = Resource.extend({
2828
comments: hasMany('comments')
2929
});
3030

31+
export const Person = Resource.extend({
32+
type: 'people',
33+
name: attr()
34+
});
35+
36+
export const Employee = Person.extend({
37+
type: 'employees',
38+
supervisor: hasOne({ resource: 'supervisor', type: 'people' })
39+
});
40+
41+
export const Supervisor = Employee.extend({
42+
type: 'supervisors',
43+
directReports: hasMany({ resource: 'employees', type: 'people' })
44+
});
45+
3146
export function setup() {
3247
const opts = { instantiate: false, singleton: false };
3348
Post.prototype.container = this.container;
@@ -38,6 +53,12 @@ export function setup() {
3853
this.registry.register('model:comments', Comment, opts);
3954
Commenter.prototype.container = this.container;
4055
this.registry.register('model:commenters', Commenter, opts);
56+
Person.prototype.container = this.container;
57+
this.registry.register('model:persons', Person, opts);
58+
Employee.prototype.container = this.container;
59+
this.registry.register('model:employees', Employee, opts);
60+
Supervisor.prototype.container = this.container;
61+
this.registry.register('model:supervisors', Supervisor, opts);
4162
}
4263

4364
export function teardown() {

tests/unit/adapters/application-test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,36 @@ test('#findRelated', function(assert) {
104104
assert.ok(service.fetch.calledWith(expectURL, { method: 'GET' }), 'url for relation passed to service#fetch');
105105
});
106106

107+
test('#findRelated can be called with optional type for the resource', function (assert) {
108+
assert.expect(4);
109+
const done = assert.async();
110+
let PersonAdapter = Adapter.extend({type: 'people', url: '/people'});
111+
PersonAdapter.reopenClass({ isServiceFactory: true });
112+
this.registry.register('service:people', PersonAdapter.extend());
113+
let service = this.container.lookup('service:people');
114+
let stub = sandbox.stub(service, 'findRelated', function () { return Ember.RSVP.Promise.resolve(null); });
115+
let resource = this.container.lookupFactory('model:employees').create({
116+
type: 'employees',
117+
id: 1000001,
118+
name: 'The Special',
119+
relationships: {
120+
supervisor: {
121+
links: {
122+
related: 'http://locahost:3000/api/v1/employees/1/supervisor'
123+
}
124+
}
125+
}
126+
});
127+
let url = resource.get( ['relationships', 'supervisor', 'links', 'related'].join('.') );
128+
resource.get('supervisor').then(function() {
129+
assert.ok(stub.calledOnce, 'people service findRelated method called once');
130+
assert.equal(stub.firstCall.args[0].resource, 'supervisor', 'findRelated called with supervisor resource');
131+
assert.equal(stub.firstCall.args[0].type, 'people', 'findRelated called with people type');
132+
assert.equal(stub.firstCall.args[1], url, 'findRelated called with url, ' + url);
133+
done();
134+
});
135+
});
136+
107137
test('#createResource', function(assert) {
108138
const adapter = this.subject({type: 'posts', url: '/posts'});
109139
adapter.serializer = { serialize: function () { return postMock; } };

0 commit comments

Comments
 (0)