Skip to content

Commit

Permalink
fix: url with query params
Browse files Browse the repository at this point in the history
This PR will correctly construct a URL where the ember-api-action path
contains query params and/or where the ember data adapter buildURL
returns a url containing query params.

Fixes #414
  • Loading branch information
mrloop committed Mar 22, 2021
1 parent 5c1e575 commit 7bbf27a
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 17 deletions.
19 changes: 16 additions & 3 deletions addon/utils/build-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,24 @@ export function buildOperationUrl<M extends Model>(
return baseUrl;
}

if (baseUrl.charAt(baseUrl.length - 1) === '/') {
return `${baseUrl}${path}`;
let url;
const [baseUrlNoQueries, baseQueries] = baseUrl.split('?');
const [pathNoQueries, pathQueries] = path.split('?');

if (baseUrlNoQueries.charAt(baseUrl.length - 1) === '/') {
url = `${baseUrlNoQueries}${pathNoQueries}`;
} else {
return `${baseUrl}/${path}`;
url = `${baseUrlNoQueries}/${pathNoQueries}`;
}

if (baseQueries || pathQueries) {
const baseSearchParams = new URLSearchParams(baseQueries);
const pathSearchParams = new URLSearchParams(pathQueries);
for (const [k, v] of pathSearchParams) { baseSearchParams.append(k, v) };
url = `${url}?${baseSearchParams.toString()}`;
}

return url;
}

export default buildOperationUrl;
32 changes: 32 additions & 0 deletions tests/acceptance/index-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,36 @@ module('Acceptance | index2', hooks => {

(assert as any).dom(`[data-test-fruit-name="Completely Eaten apple"]`).exists();
});

test('query params', async function(assert) {
await visit('/');
assert.expect(9);

this.server.get('/vegatables/:id/info', (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');
return [200, {}, '{"status": "ok"}'];
});

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');
return [200, {}, '{"status": "ok"}'];
});

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

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

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

await click('.all-vegatables .all-info-button');
});
});
8 changes: 8 additions & 0 deletions tests/dummy/app/adapters/vegatable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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')}`;
}
}
9 changes: 9 additions & 0 deletions tests/dummy/app/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ export default Controller.extend({
},
juiceAllFruit(fruit) {
Fruit.juiceAll({ was_eaten: true });
},
vegatableInfo(vegatable) {
vegatable.info({ vegatableId: vegatable.id });
},
vegatableMoreInfo(vegatable) {
vegatable.moreInfo({ vegatableId: vegatable.id });
},
allInfoVegatables(vegatable) {
vegatable.allInfo();
}
}
// END-SNIPPET
Expand Down
15 changes: 15 additions & 0 deletions tests/dummy/app/models/vegatable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// BEGIN-SNIPPET vegatable-model
import DS from 'ember-data';
import { collectionAction, memberAction } from 'ember-api-actions';

const { attr, Model } = DS;

const Vegatable = Model.extend({
name: attr('string'),
info: memberAction({ path: 'info', type: 'get' }),
moreInfo: memberAction({ path: 'moreInfo?more=true', type: 'get' }),
allInfo: collectionAction({ path: 'allInfo?less=false', type: 'get' }),
});

export default Vegatable;
// END-SNIPPET
54 changes: 47 additions & 7 deletions tests/dummy/app/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Pretender from 'pretender';

const { testing } = Ember;

const LEGACY_PAYLOAD = {
const LEGACY_FRUIT_PAYLOAD = {
fruit: [
{
id: 1,
Expand All @@ -26,7 +26,20 @@ const LEGACY_PAYLOAD = {
]
};

const PAYLOAD = {
const LEGACY_VEGATABLE_PAYLOAD = {
vegatable: [
{
id: 1,
name: 'potato',
},
{
id: 2,
name: 'carrot',
}
]
}

const FRUIT_PAYLOAD = {
data: [
{
type: 'fruit',
Expand Down Expand Up @@ -55,23 +68,50 @@ const PAYLOAD = {
attributes: {
name: 'grape'
}
},
]
};

const VEGATABLE_PAYLOAD = {
data: [
{
type: 'vegatable',
id: 1,
attributes: {
name: 'potato'
}
},
{
type: 'vegatable',
id: 2,
attributes: {
name: 'carrot'
}
}
]
};




export default Route.extend({
server: undefined as any,
requests: [] as any[],
currentModel: undefined as any,
model() {
let arr: any = [];
this.store.pushPayload('fruit', !this.store.peekAll ? LEGACY_PAYLOAD : PAYLOAD);
let fruitArr: any = [];
let vegatableArr: any = [];
this.store.pushPayload('fruit', !this.store.peekAll ? LEGACY_FRUIT_PAYLOAD : FRUIT_PAYLOAD);
this.store.pushPayload('vegatable', !this.store.peekAll ? LEGACY_VEGATABLE_PAYLOAD : VEGATABLE_PAYLOAD);
if (!this.store.peekAll) {
arr = [1, 2, 3, 4].map(id => (this.store as any).getById('fruit', id));
fruitArr = [1, 2, 3, 4].map(id => (this.store as any).getById('fruit', id));
vegatableArr = [1, 2].map(id => (this.store as any).getById('vegatable', id));
} else {
arr = this.store.peekAll('fruit');
fruitArr = this.store.peekAll('fruit');
vegatableArr = this.store.peekAll('vegatable');
}
return A(arr);

return { fruit: A(fruitArr), vegatable: A(vegatableArr) };
},

beforeModel() {
Expand Down
49 changes: 42 additions & 7 deletions tests/dummy/app/templates/index.hbs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<div class="row">
<div class="col m6 s12">
<h4>API actions on an individual resource</h4>
{{#each model as |fruit|}}

<h5>Fruit</h5>
{{#each model.fruit as |fruit|}}
<p class="fruit-thing" id={{fruit.name}}>
{{#x-btn
class="ripen-instance-button yellow"
click=(action "ripenFruit" fruit)
}}
Ripen
{{/x-btn}}
{{/x-btn}}
{{#x-btn
class="info-instance-button indigo white-text"
click=(action "fruitInfo" fruit)
Expand All @@ -23,34 +25,67 @@
{{#x-btn class="eat-instance-button yellow" click=(action "eatFruit" fruit)}}Eat{{/x-btn}}
</p>
{{/each}}

<h5>Vegatable</h5>
{{#each model.vegatable as |vegatable|}}
<p class="vegatable-thing" id={{vegatable.name}}>
{{#x-btn
class="info-instance-button indigo white-text"
click=(action "vegatableInfo" vegatable)
}}
Info
{{/x-btn}}
</p>

<p class="vegatable-thing" id={{vegatable.name}}>
{{#x-btn
class="more-info-instance-button yellow white-text"
click=(action "vegatableMoreInfo" vegatable)
}}
More Info
{{/x-btn}}
</p>
{{/each}}

<h4>API action on a collection of resources</h4>
<p class="all-vegatables">
{{content.vegatable.constructor.modelName}}
{{#x-btn
class="all-info-button indigo white-text"
click=(action "allInfoVegatables"
(first-in-array (or content.vegatable model.vegatable)))
}}
All Info
{{/x-btn}}
</p>

<p class="all-fruit">
{{content.constructor.modelName}}
{{content.fruit.constructor.modelName}}
{{#x-btn
class="ripen-type-button yellow"
click=(action "ripenAllFruit" (first-in-array (or content model)))
click=(action "ripenAllFruit" (first-in-array (or content.fruit model.fruit)))
}}
Ripen All
{{/x-btn}}
{{#x-btn
class="fresh-type-button indigo white-text"
click=(action "getAllFreshFruit"
(first-in-array (or content model)))
(first-in-array (or content.fruit model.fruit)))
}}
Get Fresh
{{/x-btn}}
{{#x-btn
class="fresh-type-button indigo white-text"
click=(action "juiceAllFruit"
(first-in-array (or content model)))
(first-in-array (or content.fruit model.fruit)))
}}
Juice Everything
{{/x-btn}}

{{#x-btn
class="eat-all-button indigo white-text"
click=(action "eatAll"
(first-in-array (or content model)))
(first-in-array (or content.fruit model.fruit)))
}}
Eat all
{{/x-btn}}
Expand Down

0 comments on commit 7bbf27a

Please sign in to comment.