Skip to content

Commit eabe4e3

Browse files
committed
[RFC-774] Deprecate implicit record loading in Ember Route
1 parent c682675 commit eabe4e3

File tree

4 files changed

+119
-39
lines changed

4 files changed

+119
-39
lines changed

packages/@ember/routing/route.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { isProxy, lookupDescriptor } from '@ember/-internals/utils';
1717
import type { AnyFn } from '@ember/-internals/utility-types';
1818
import Controller from '@ember/controller';
1919
import type { ControllerQueryParamType } from '@ember/controller';
20-
import { assert, info, isTesting } from '@ember/debug';
20+
import { assert, deprecate, info, isTesting } from '@ember/debug';
2121
import EngineInstance from '@ember/engine/instance';
2222
import { dependentKeyCompat } from '@ember/object/compat';
2323
import { once } from '@ember/runloop';
@@ -66,6 +66,16 @@ type RouteTransitionState<R extends Route> = TransitionState<R> & {
6666
type MaybeParameters<T> = T extends AnyFn ? Parameters<T> : unknown[];
6767
type MaybeReturnType<T> = T extends AnyFn ? ReturnType<T> : unknown;
6868

69+
interface StoreLike {
70+
find(type: string, value: unknown): unknown;
71+
}
72+
73+
function isStoreLike(store: unknown): store is StoreLike {
74+
return (
75+
typeof store === 'object' && store !== null && typeof (store as StoreLike).find === 'function'
76+
);
77+
}
78+
6979
export const ROUTE_CONNECTIONS = new WeakMap();
7080
const RENDER = Symbol('render');
7181

@@ -1479,8 +1489,22 @@ class Route<T = unknown>
14791489
@param {Object} value the value passed to find
14801490
@private
14811491
*/
1482-
findModel(...args: any[]) {
1483-
return (get(this, 'store') as any).find(...args);
1492+
findModel(type: string, value: unknown) {
1493+
deprecate(
1494+
`The implicit model loading behavior for routes is deprecated. Please define an explicit model hook for ${this.fullRouteName}.`,
1495+
false,
1496+
{
1497+
id: 'deprecate-implicit-route-model',
1498+
for: 'ember-source',
1499+
since: { available: '4.12.0' },
1500+
until: '6.0.0',
1501+
}
1502+
);
1503+
1504+
const store = get(this, 'store');
1505+
assert('Expected route to have a store with a find method', isStoreLike(store));
1506+
1507+
return store.find(type, value);
14841508
}
14851509

14861510
/**

packages/@ember/routing/tests/system/route_test.js

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ moduleFor(
2323
}
2424

2525
['@test default store utilizes the container to acquire the model factory'](assert) {
26-
assert.expect(4);
26+
assert.expect(5);
2727

2828
let Post = EmberObject.extend();
2929
let post = {};
@@ -55,8 +55,14 @@ moduleFor(
5555
let owner = buildOwner(ownerOptions);
5656
setOwner(route, owner);
5757

58-
assert.equal(route.model({ post_id: 1 }), post);
59-
assert.equal(route.findModel('post', 1), post, '#findModel returns the correct post');
58+
expectDeprecation(
59+
() =>
60+
ignoreAssertion(() => {
61+
assert.equal(route.model({ post_id: 1 }), post);
62+
assert.equal(route.findModel('post', 1), post, '#findModel returns the correct post');
63+
}),
64+
/The implicit model loading behavior for routes is deprecated./
65+
);
6066

6167
runDestroy(owner);
6268
}
@@ -77,21 +83,23 @@ moduleFor(
7783

7884
route = owner.lookup('route:index');
7985

80-
expectAssertion(function () {
81-
route.findModel('post', 1);
82-
}, `You used the dynamic segment \`post_id\` in your route ` +
83-
`\`index\` for which Ember requires you provide a ` +
84-
`data-loading implementation. Commonly, that is done by ` +
85-
`adding a model hook implementation on the route ` +
86-
`(\`model({post_id}) {\`) or by injecting an implemention of ` +
87-
`a data store: \`@service store;\`.\n\n` +
88-
`Rarely, applications may attempt to use a legacy behavior where ` +
89-
`the model class (in this case \`post\`) is resolved and the ` +
90-
`\`find\` method on that class is invoked to load data. In this ` +
91-
`application, a model of \`post\` was found but it did not ` +
92-
`provide a \`find\` method. You should not add a \`find\` ` +
93-
`method to your model. Instead, please implement an appropriate ` +
94-
`\`model\` hook on the \`index\` route.`);
86+
ignoreDeprecation(() =>
87+
expectAssertion(function () {
88+
route.findModel('post', 1);
89+
}, `You used the dynamic segment \`post_id\` in your route ` +
90+
`\`index\` for which Ember requires you provide a ` +
91+
`data-loading implementation. Commonly, that is done by ` +
92+
`adding a model hook implementation on the route ` +
93+
`(\`model({post_id}) {\`) or by injecting an implemention of ` +
94+
`a data store: \`@service store;\`.\n\n` +
95+
`Rarely, applications may attempt to use a legacy behavior where ` +
96+
`the model class (in this case \`post\`) is resolved and the ` +
97+
`\`find\` method on that class is invoked to load data. In this ` +
98+
`application, a model of \`post\` was found but it did not ` +
99+
`provide a \`find\` method. You should not add a \`find\` ` +
100+
`method to your model. Instead, please implement an appropriate ` +
101+
`\`model\` hook on the \`index\` route.`)
102+
);
95103

96104
runDestroy(owner);
97105
}
@@ -109,14 +117,16 @@ moduleFor(
109117

110118
route = owner.lookup('route:index');
111119

112-
expectAssertion(function () {
113-
route.model({ post_id: 1 });
114-
}, `You used the dynamic segment \`post_id\` in your route ` +
115-
`\`index\` for which Ember requires you provide a ` +
116-
`data-loading implementation. Commonly, that is done by ` +
117-
`adding a model hook implementation on the route ` +
118-
`(\`model({post_id}) {\`) or by injecting an implemention of ` +
119-
`a data store: \`@service store;\`.`);
120+
ignoreDeprecation(() =>
121+
expectAssertion(function () {
122+
route.model({ post_id: 1 });
123+
}, `You used the dynamic segment \`post_id\` in your route ` +
124+
`\`index\` for which Ember requires you provide a ` +
125+
`data-loading implementation. Commonly, that is done by ` +
126+
`adding a model hook implementation on the route ` +
127+
`(\`model({post_id}) {\`) or by injecting an implemention of ` +
128+
`a data store: \`@service store;\`.`)
129+
);
120130

121131
runDestroy(owner);
122132
}
@@ -130,9 +140,7 @@ moduleFor(
130140

131141
route = owner.lookup('route:index');
132142

133-
ignoreAssertion(function () {
134-
route.model({ post_id: 1 });
135-
});
143+
ignoreDeprecation(() => ignoreAssertion(() => route.model({ post_id: 1 })));
136144

137145
assert.ok(true, 'no error was raised');
138146

packages/ember/tests/routing/decoupled_basic_test.js

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,29 @@ moduleFor(
130130

131131
this.add('model:menu_item', MenuItem);
132132

133+
let SpecialRoute = class extends Route {
134+
model({ menu_item_id }) {
135+
return MenuItem.find(menu_item_id);
136+
}
137+
};
138+
139+
this.add('route:special', SpecialRoute);
140+
133141
this.addTemplate('special', '<p>{{@model.id}}</p>');
134142
this.addTemplate('loading', '<p>LOADING!</p>');
135143

136-
let visited = runTask(() => this.visit('/specials/1'));
137-
this.assertText('LOADING!', 'The app is in the loading state');
144+
let promise;
145+
ignoreDeprecation(() => {
146+
let visited = runTask(() => this.visit('/specials/1'));
147+
this.assertText('LOADING!', 'The app is in the loading state');
138148

139-
resolve(menuItem);
149+
resolve(menuItem);
140150

141-
return visited.then(() => {
142-
this.assertText('1', 'The app is now in the specials state');
151+
promise = visited.then(() => {
152+
this.assertText('1', 'The app is now in the specials state');
153+
});
143154
});
155+
return promise;
144156
}
145157

146158
[`@test The loading state doesn't get entered for promises that resolve on the same run loop`](
@@ -160,6 +172,14 @@ moduleFor(
160172

161173
this.add('model:menu_item', MenuItem);
162174

175+
let SpecialRoute = class extends Route {
176+
model({ menu_item_id }) {
177+
return MenuItem.find(menu_item_id);
178+
}
179+
};
180+
181+
this.add('route:special', SpecialRoute);
182+
163183
this.add(
164184
'route:loading',
165185
Route.extend({
@@ -218,7 +238,9 @@ moduleFor(
218238
})
219239
);
220240

221-
runTask(() => handleURLRejectsWith(this, assert, 'specials/1', 'Setup error'));
241+
ignoreDeprecation(() => {
242+
runTask(() => handleURLRejectsWith(this, assert, 'specials/1', 'Setup error'));
243+
});
222244

223245
resolve(menuItem);
224246
}
@@ -262,6 +284,10 @@ moduleFor(
262284
this.add(
263285
'route:special',
264286
Route.extend({
287+
model({ menu_item_id }) {
288+
return MenuItem.find(menu_item_id);
289+
},
290+
265291
setup() {
266292
throw new Error('Setup error');
267293
},

packages/ember/tests/routing/model_loading_test.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,14 @@ moduleFor(
361361
});
362362
this.add('model:menu_item', MenuItem);
363363

364+
let SpecialRoute = class extends Route {
365+
model({ menu_item_id }) {
366+
return MenuItem.find(menu_item_id);
367+
}
368+
};
369+
370+
this.add('route:special', SpecialRoute);
371+
364372
this.router.map(function () {
365373
this.route('home', { path: '/' });
366374
this.route('special', { path: '/specials/:menu_item_id' });
@@ -389,6 +397,13 @@ moduleFor(
389397
});
390398
this.add('model:menu_item', MenuItem);
391399

400+
let SpecialRoute = class extends Route {
401+
model({ menu_item_id }) {
402+
return MenuItem.find(menu_item_id);
403+
}
404+
};
405+
this.add('route:special', SpecialRoute);
406+
392407
this.addTemplate('home', '<h3>Home</h3>');
393408
this.addTemplate('special', '<p>{{@model.id}}</p>');
394409

@@ -813,13 +828,20 @@ moduleFor(
813828
['@test Route model hook finds the same model as a manual find'](assert) {
814829
let post;
815830
let Post = EmberObject.extend();
816-
this.add('model:post', Post);
817831
Post.reopenClass({
818832
find() {
819833
post = this;
820834
return {};
821835
},
822836
});
837+
this.add('model:post', Post);
838+
839+
let PostRoute = class extends Route {
840+
model({ post_id }) {
841+
return Post.find(post_id);
842+
}
843+
};
844+
this.add('route:post', PostRoute);
823845

824846
this.router.map(function () {
825847
this.route('post', { path: '/post/:post_id' });

0 commit comments

Comments
 (0)