Skip to content

Commit

Permalink
feat: adapterOptions
Browse files Browse the repository at this point in the history
Allows api actions to take an optional `options` object. If you pass an
object on the adapterOptions property of the options argument it will be
passed to your adapter via the snapshot.

Similar to ember data Store and Modal functions, for example
https://api.emberjs.com/ember-data/3.25/classes/Store/methods/findAll?anchor=findAll
https://api.emberjs.com/ember-data/3.25/classes/Model/methods/save?anchor=save

This is similar to #214 with tests.

Closes #214
  • Loading branch information
mrloop committed Mar 22, 2021
1 parent 7bbf27a commit be19fe5
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 7 deletions.
2 changes: 2 additions & 0 deletions addon/utils/build-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function buildOperationUrl<M extends Model>(
record: M,
opPath: string,
urlType: EmberDataRequestType,
adapterOptions: any,
instance = true
) {
const modelClass = _getModelClass(record);
Expand All @@ -56,6 +57,7 @@ export function buildOperationUrl<M extends Model>(
const adapter = store.adapterFor(modelName);
const path = opPath;
const snapshot = snapshotFromRecord(record);
snapshot.adapterOptions = adapterOptions;
const baseUrl = adapter.buildURL(modelName, instance ? record.get('id') : null, snapshot, urlType);

if (!path) {
Expand Down
8 changes: 6 additions & 2 deletions addon/utils/collection-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ export interface CollectionOperationOptions<IN, OUT> {
}

export default function collectionOp<IN = any, OUT = any>(options: CollectionOperationOptions<IN, OUT>) {
return function runCollectionOp(this: Model, payload: IN): Promise<OUT> {
return function runCollectionOp(
this: Model,
payload: IN,
collectionOptions: any = {}
): Promise<OUT> {
const model: Model = this;
const recordClass = _getModelClass(model);
const modelName = _getModelName(recordClass);
const store = _getStoreFromRecord(model);
const requestType: HTTPVerb = strictifyHttpVerb(options.type || 'put');
const urlType: EmberDataRequestType = options.urlType || 'updateRecord';
const adapter = store.adapterFor(modelName);
const fullUrl = buildOperationUrl(model, options.path, urlType, false);
const fullUrl = buildOperationUrl(model, options.path, urlType, collectionOptions.adapterOptions, false);
const data = (options.before && options.before.call(model, payload)) || payload;
return adapter
.ajax(fullUrl, requestType, assign(options.ajaxOptions || {}, { data }))
Expand Down
8 changes: 6 additions & 2 deletions addon/utils/member-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@ export interface InstanceOperationOptions<IN, OUT> {
}

export default function instanceOp<IN = any, OUT = any>(options: InstanceOperationOptions<IN, OUT>) {
return function runInstanceOp(this: Model, payload: IN): Promise<OUT> {
return function runInstanceOp(
this: Model,
payload: IN,
instanceOptions:any = {}
): Promise<OUT> {
const recordClass = _getModelClass(this);
const modelName = _getModelName(recordClass);
const store = _getStoreFromRecord(this);
const { ajaxOptions, path, before, after, type = 'put', urlType = 'updateRecord' } = options;
const requestType: HTTPVerb = strictifyHttpVerb(type);
const adapter = store.adapterFor(modelName);
const fullUrl = buildOperationUrl(this, path, urlType);
const fullUrl = buildOperationUrl(this, path, urlType, instanceOptions.adapterOptions);
const data = (before && before.call(this, payload)) || payload;
return adapter.ajax(fullUrl, requestType, assign(ajaxOptions || {}, { data })).then((response: JSONValue) => {
if (after && !this.isDestroyed) {
Expand Down
34 changes: 34 additions & 0 deletions tests/acceptance/index-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,38 @@ module('Acceptance | index2', hooks => {

await click('.all-vegatables .all-info-button');
});

test('adapterOptions', async function(assert) {
await visit('/');
assert.expect(11);

this.server.get('/vegatables/:id/moreInfo', (request) => {
assert.equal(request.params.id, '1', 'request made to the right URL');
assert.equal(request.queryParams.vegatableId, '1', 'request made with the right query params');
assert.equal(request.queryParams.vegatable, 'potato', 'request made with the right buildURL query params');
assert.equal(request.queryParams.extra, 'false', 'request made with the right adapterOptions query params');
return [200, {}, '{"status": "ok"}'];
});

await click('#potato .options-instance-button');

this.server.get('/vegatables/:id/moreInfo', (request) => {
assert.equal(request.params.id, '2', 'request made to the right URL');
assert.equal(request.queryParams.vegatableId, '2', 'request made with the right query params');
assert.equal(request.queryParams.vegatable, 'carrot', 'request made with the right buildURL query params');
assert.equal(request.queryParams.more, 'true', 'request made with the right path query params');
assert.equal(request.queryParams.extra, 'false', 'request made with the right adapterOptions query params');
return [200, {}, '{"status": "ok"}'];
});

await click('#carrot .options-instance-button');

this.server.get('/vegatables/allInfo', (request) => {
assert.equal(request.queryParams.vegatable, 'potato', 'request made with the right buildURL query params');
assert.equal(request.queryParams.extra, 'false', 'request made with the right adapterOptions query params');
return [200, {}, '{"status": "ok"}'];
});

await click('.all-vegatables .all-info-button');
});
});
12 changes: 10 additions & 2 deletions tests/dummy/app/adapters/vegatable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import ApplicationAdapter from './application';

export default class VegatableAdapter extends ApplicationAdapter {
public buildURL(modelName: string, id: string|[string]|object, snapshot: object, requestType: string, query: object) {
const url = super.buildURL(modelName, id, snapshot, requestType, query);
return `${url}?${modelName}=${snapshot.attr('name')}`;
const urlStr = super.buildURL(modelName, id, snapshot, requestType, query);
const [path, searchStr] = urlStr.split('?');
const searchParams = new URLSearchParams(searchStr);
// for testing buildURL queryParams
searchParams.append(modelName, snapshot.attr('name'));
// for testing adapterOptions
for (const [k, v] of Object.entries(snapshot.adapterOptions || {})) {
searchParams.append(k, v.toString());
}
return `${path}?${searchParams.toString()}`;
}
}
5 changes: 4 additions & 1 deletion tests/dummy/app/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ export default Controller.extend({
vegatableMoreInfo(vegatable) {
vegatable.moreInfo({ vegatableId: vegatable.id });
},
vegatableOptions(vegatable) {
vegatable.moreInfo({ vegatableId: vegatable.id }, { adapterOptions: { extra: 'false' }});
},
allInfoVegatables(vegatable) {
vegatable.allInfo();
vegatable.allInfo({}, { adapterOptions: { extra: 'false' }});
}
}
// END-SNIPPET
Expand Down
9 changes: 9 additions & 0 deletions tests/dummy/app/templates/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@
More Info
{{/x-btn}}
</p>

<p class="vegatable-thing" id={{vegatable.name}}>
{{#x-btn
class="options-instance-button yellow white-text"
click=(action "vegatableOptions" vegatable)
}}
Options
{{/x-btn}}
</p>
{{/each}}

<h4>API action on a collection of resources</h4>
Expand Down

0 comments on commit be19fe5

Please sign in to comment.