forked from rust-lang/rfcs
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request rust-lang#372 from runspired/ember-data-custom-rec…
…ords [RFC ember-data] modelFactoryFor
- Loading branch information
Showing
1 changed file
with
190 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
- Start Date: 2018-09-06 | ||
- RFC PR: (leave this empty) | ||
- Ember Issue: (leave this empty) | ||
|
||
# ember-data | modelFactoryFor | ||
|
||
## Summary | ||
|
||
Promote the private `store._modelFactoryFor` to public API as `store.modelFactoryFor`. | ||
|
||
## Motivation | ||
|
||
This RFC is a follow-up RFC for [#293 RecordData](/~https://github.com/emberjs/rfcs/pull/293). | ||
|
||
Ember differentiates between `klass` and `factory` for classes registered with the container. | ||
At times, `ember-data` needs the `klass`, at other times, it needs the `factory`. For this reason, | ||
`ember-data` has carried two APIs for accessing one or the other for some time. The public `modelFor` | ||
provides access to the `klass` where schema information is stored, while the private `_modelFactoryFor` | ||
provides access to the factory for instantiation. | ||
|
||
We provide access to the class with `modelFor` roughly implemented as `store._modelFactoryFor(modelName).klass`. | ||
We instantiate records from this class roughly implemented as `store._modelFactoryFor(modelName).create({ ...args })`. | ||
|
||
For symmetry, both of these APIs should be public. Making `modelFactoryFor` public would provide a hook | ||
that consumers can override should they desire to provide a custom `ModelClass` as an alternative | ||
to `DS.Model`. | ||
|
||
## Detailed design | ||
Due to previous complexity in the lookup of models in `ember-data`, we previously had both `modelFactoryFor` | ||
and `_modelFactoryFor`. Despite the naming, both of these methods were private. During a recent cleanup phase, | ||
we unified the methods into `_modelFactoryFor` and left a deprecation in `modelFactoryFor`. This RFC proposes | ||
un-deprecating the `modelFactoryFor` method and making it public, while deprecating the private `_modelFactoryFor`. | ||
|
||
More precisely: | ||
|
||
- `store._modelFactoryFor` becomes deprecated and calls `store.modelFactoryFor`. | ||
- `store.modelFactoryFor` becomes un-deprecated. | ||
|
||
### The contract for `modelFactoryFor` | ||
|
||
The return value of `modelFactoryFor` MUST be the result of a call to [`applicationInstance.factoryFor`](https://www.emberjs.com/api/ember/3.4/classes/ApplicationInstance/methods/factoryFor?anchor=factoryFor) | ||
where `applicationInstance` is the `owner` returned by using `getOwner(this)` to access the `owner` of the `store` instance. | ||
|
||
```typescript | ||
interface Klass {} | ||
|
||
interface Factory { | ||
klass: Klass, | ||
create(): Klass | ||
} | ||
|
||
interface FactoryMap { | ||
[factoryName: string]: Factory | ||
} | ||
|
||
declare function factoryFor<K extends keyof FactoryMap>(factoryName: K): FactoryMap[K]; | ||
|
||
interface Store { | ||
modelFactoryFor(modelName: string): ReturnType<typeof factoryFor>; | ||
} | ||
``` | ||
|
||
Users interested in providing a custom class for their `records` and who override `modelFactoryFor`, | ||
would not need to also change `modelFor`, as this would be the `klass` accessible via the `factory`. | ||
|
||
Users wishing to extend the behavior of `modelFactoryFor` could do so in the following manner: | ||
|
||
**Example 1:** | ||
|
||
**services/store.js** | ||
```js | ||
import { getOwner } from '@ember/application'; | ||
import Store from 'ember-data/store'; | ||
|
||
export default Store.extend({ | ||
modelFactoryFor(modelName) { | ||
if (someCustomCondition) { | ||
return getOwner(this).factoryFor(someFactoryName); | ||
} | ||
|
||
return this._super(modelName); | ||
} | ||
}); | ||
``` | ||
|
||
#### `Model.modelName` | ||
|
||
`ember-data` currently sets `modelName` onto the `klass` accessible via the `factory`. For classes that do not | ||
inherit from `DS.Model` this would not be done, although end users may do so themselves in their implementations | ||
if so desired. | ||
|
||
### What is a valid factory? | ||
|
||
The default export of a custom ModelClass **MUST** conform to the requirements of `Ember.factoryFor`. The requirements | ||
of `factoryFor` are currently underspecified; however, in practice, this means that the default export is an | ||
instantiable class with a static `create` method and an instance `destroy` method or that inherits from `EmberObject` | ||
(which provides such methods). | ||
|
||
**Example 2:** | ||
|
||
```javascript | ||
import { assign } from '@ember/polyfills'; | ||
|
||
export default class CustomModel { | ||
constructor(createArgs) { | ||
assign(this, createArgs); | ||
} | ||
destroy() { | ||
// ... do teardown | ||
} | ||
static create(createArgs) { | ||
return new this(createArgs); | ||
} | ||
} | ||
``` | ||
|
||
**Example 3:** | ||
|
||
```javascript | ||
import EmberObject from '@ember/object'; | ||
|
||
export default class CustomModel extends EmberObject { | ||
constructor(createArgs) { | ||
super(createArgs); | ||
} | ||
} | ||
``` | ||
|
||
Custom classes for models should expect their constructor to receive a single argument: an object with *at least* | ||
the following. | ||
|
||
- A `recordData` instance accessible via `getRecordData` (see below) | ||
- Any properties passed as the second arg to `createRecord` | ||
- An `owner` accessible via `Ember.getOwner` | ||
- Any DI injections | ||
- any other properties that `Ember` chooses to pass to a class instantiated via `factory.create` (currently none) | ||
|
||
### getRecordData | ||
|
||
Every `record` (instance of the class returned by `modelFactoryFor`) will have an associated [RecordData](/~https://github.com/emberjs/rfcs/pull/293) | ||
which contains the backing data for the id, type, attributes and relationships of that record. | ||
|
||
This backing data can be accessed by using the `getRecordData` util on the `record` (or on the `createArgs` passed to | ||
a record). Using `getRecordData` on a `record` is only guaranteed after the record has been instantiated. During | ||
instantiation, this call should be made on the `createArgs` object passed into the record. | ||
|
||
**Example 4** | ||
|
||
```javascript | ||
import { getRecordData } from 'ember-data'; | ||
|
||
export default class CustomModel { | ||
constructor(createArgs) { | ||
// during instantiation, `recordData` is available by calling `getRecordData` on createArgs | ||
let recordData = getRecordData(createArgs); | ||
} | ||
someMethod() { | ||
// post instantiation, `recordData` is available by calling `getRecordData` on the instance | ||
let recordData = getRecordData(this); | ||
} | ||
destroy() { | ||
// ... do teardown | ||
} | ||
static create(createArgs) { | ||
return new this(createArgs); | ||
} | ||
} | ||
``` | ||
|
||
## How we teach this | ||
|
||
This API would be intended for addon-authors and power users. It is not expected | ||
that most apps would implement custom models, much as it is not expected that most | ||
apps would implement custom `RecordData`. The teaching story would be limited to | ||
documenting the nature and purpose of `modelFactoryFor`. | ||
|
||
## Drawbacks | ||
|
||
- Users may try to use the hook to instantiate records on their own. Ultimately, the store | ||
should still do the instantiating. | ||
|
||
## Alternatives | ||
|
||
Users could define models in `models/*.js` that utilize a custom `ModelClass`. | ||
However, such an API for custom classes would exclude the ability to dynamically | ||
generate classes. | ||
|
||
## Unresolved questions | ||
|
||
None |