From c2ed116b68340059844cce9c1e92d9aa34592228 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 2 Oct 2023 11:39:28 +0200 Subject: [PATCH 01/25] #3 Write a section about migrating from Adapters --- app/routes/application.js | 169 ++---------------- snippets/adapters/cache-lifetime/new.js | 12 ++ snippets/adapters/cache-lifetime/old.js | 41 +++++ snippets/adapters/host-and-namespace/new.js | 7 + snippets/adapters/host-and-namespace/old.js | 7 + snippets/adapters/path-for-type/new.js | 12 ++ snippets/adapters/path-for-type/old.js | 6 + snippets/adapters/path-for-type/utils.js | 60 +++++++ .../adapters/cache-lifetime/en-us.yaml | 4 + translations/adapters/en-us.yaml | 1 + translations/adapters/general/en-us.yaml | 9 + .../adapters/host-and-namespace/en-us.yaml | 5 + .../adapters/path-for-type/en-us.yaml | 5 + translations/en-us.yaml | 2 +- 14 files changed, 181 insertions(+), 159 deletions(-) create mode 100644 snippets/adapters/cache-lifetime/new.js create mode 100644 snippets/adapters/cache-lifetime/old.js create mode 100644 snippets/adapters/host-and-namespace/new.js create mode 100644 snippets/adapters/host-and-namespace/old.js create mode 100644 snippets/adapters/path-for-type/new.js create mode 100644 snippets/adapters/path-for-type/old.js create mode 100644 snippets/adapters/path-for-type/utils.js create mode 100644 translations/adapters/cache-lifetime/en-us.yaml create mode 100644 translations/adapters/en-us.yaml create mode 100644 translations/adapters/general/en-us.yaml create mode 100644 translations/adapters/host-and-namespace/en-us.yaml create mode 100644 translations/adapters/path-for-type/en-us.yaml diff --git a/app/routes/application.js b/app/routes/application.js index 654a0c4..d4fac6a 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -4,172 +4,25 @@ export default class ApplicationRoute extends Route { model() { return [ { - id: 'generating-files', + id: 'adapters', subsections: [ { - id: 'generating-component', - classicFiles: ['classic.shell'], - octaneFiles: ['octane.shell'], + id: 'general', }, { - id: 'file-structure', - classicFiles: ['classic.text'], - octaneFiles: ['octane.text'], - }, - ], - }, - { - id: 'component-templates', - subsections: [ - { - id: 'angle-brackets', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'inline-vs-block', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'angle-brackets-nested', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'template-named', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'template-this', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'template-arguments-named', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'template-arguments-this', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], - }, - { - id: 'tag-name', - classicFiles: ['classic.hbs', 'classic.html'], - octaneFiles: ['octane.hbs', 'octane.html'], - }, - { - id: 'element-id', - classicFiles: ['classic.js', 'classic.hbs'], - octaneFiles: ['octane.js', 'octane.hbs'], - }, - ], - }, - { - id: 'component-properties', - subsections: [ - { - id: 'js-boilerplate', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'js-properties', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'ddau', - classicFiles: [ - 'classic-parent.js', - 'classic-parent.hbs', - 'classic-child.js', - 'classic-child.hbs', - ], - octaneFiles: [ - 'octane-parent.js', - 'octane-parent.hbs', - 'octane-child.hbs', - ], + id: 'host-and-namespace', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], }, { - id: 'args', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], + id: 'path-for-type', + classicFiles: ['old.js'], + octaneFiles: ['new.js', 'utils.js'], }, { - id: 'get-and-set', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'tracked-vs-cp', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'computed-decorator', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - ], - }, - { - id: 'actions', - subsections: [ - { - id: 'actions', - classicFiles: ['classic.js', 'classic.hbs'], - octaneFiles: ['octane.js', 'octane.hbs'], - }, - { - id: 'template-arguments-default', - classicFiles: ['classic.hbs', 'classic.js'], - octaneFiles: ['octane.hbs', 'octane.js'], - }, - { - id: 'mixins', - classicFiles: ['classic.js'], - octaneDescriptionKey: 'actions.mixins.octaneDescription', - }, - ], - }, - { - id: 'component-lifecycle', - subsections: [ - { - id: 'constructors', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'will-destroy', - classicFiles: ['classic.js'], - octaneFiles: ['octane.js'], - }, - { - id: 'render-modifiers', - classicDescriptionKey: - 'component-lifecycle.render-modifiers.classicDescription', - octaneFiles: ['octane.shell'], - }, - { - id: 'did-insert', - classicFiles: ['classic.hbs', 'classic.js'], - octaneFiles: ['octane.hbs', 'octane.js'], - }, - ], - }, - { - id: 'routes', - subsections: [ - { - id: 'model-access', - classicFiles: ['classic.hbs'], - octaneFiles: ['octane.hbs'], + id: 'cache-lifetime', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], }, ], }, diff --git a/snippets/adapters/cache-lifetime/new.js b/snippets/adapters/cache-lifetime/new.js new file mode 100644 index 0000000..2678c39 --- /dev/null +++ b/snippets/adapters/cache-lifetime/new.js @@ -0,0 +1,12 @@ +import { LifetimesService } from '@ember-data/request-utils'; +import DataStore from '@ember-data/store'; + +export default class Store extends DataStore { + constructor(args) { + super(args); + this.lifetimes = new LifetimesService(this, { + apiCacheSoftExpires: 30_000, + apiCacheHardExpires: 60_000 + }); + } +} diff --git a/snippets/adapters/cache-lifetime/old.js b/snippets/adapters/cache-lifetime/old.js new file mode 100644 index 0000000..58bea6d --- /dev/null +++ b/snippets/adapters/cache-lifetime/old.js @@ -0,0 +1,41 @@ +import JSONAPIAdapter from '@ember-data/adapter/json-api'; + +export default class ApplicationAdapter extends JSONAPIAdapter { + shouldBackgroundReloadAll(store, snapshotArray) { + let { downlink, effectiveType } = navigator.connection; + + return downlink > 0 && effectiveType === '4g'; + } + + shouldBackgroundReloadRecord(store, snapshot) { + let { downlink, effectiveType } = navigator.connection; + + return downlink > 0 && effectiveType === '4g'; + } + + shouldReloadRecord(store, ticketSnapshot) { + let lastAccessedAt = ticketSnapshot.attr('lastAccessedAt'); + let timeDiff = moment().diff(lastAccessedAt, 'minutes'); + + if (timeDiff > 20) { + return true; + } else { + return false; + } + } + + shouldReloadAll(store, snapshotArray) { + let snapshots = snapshotArray.snapshots(); + + return snapshots.any((ticketSnapshot) => { + let lastAccessedAt = ticketSnapshot.attr('lastAccessedAt'); + let timeDiff = moment().diff(lastAccessedAt, 'minutes'); + + if (timeDiff > 20) { + return true; + } else { + return false; + } + }); + } +} diff --git a/snippets/adapters/host-and-namespace/new.js b/snippets/adapters/host-and-namespace/new.js new file mode 100644 index 0000000..08a044f --- /dev/null +++ b/snippets/adapters/host-and-namespace/new.js @@ -0,0 +1,7 @@ +import { setBuildURLConfig } from '@ember-data/request-utils'; +import config from 'my-app/config/environment'; + +setBuildURLConfig({ + host: config.api.host, + namespace: config.api.namespace, +}); diff --git a/snippets/adapters/host-and-namespace/old.js b/snippets/adapters/host-and-namespace/old.js new file mode 100644 index 0000000..e972908 --- /dev/null +++ b/snippets/adapters/host-and-namespace/old.js @@ -0,0 +1,7 @@ +import JSONAPIAdapter from '@ember-data/adapter/json-api'; +import config from 'my-app/config/environment'; + +export default class ApplicationAdapter extends JSONAPIAdapter { + host = config.api.host; + namespace = config.api.namespace; +} diff --git a/snippets/adapters/path-for-type/new.js b/snippets/adapters/path-for-type/new.js new file mode 100644 index 0000000..7ac118e --- /dev/null +++ b/snippets/adapters/path-for-type/new.js @@ -0,0 +1,12 @@ +import { findRecord } from '@ember-data/json-api/request'; +// import { findRecord } from '@ember-data/rest/request'; +// import { findRecord } from '@ember-data/active-record/request'; + +const dynamicPathFor = (identifierType) => { + const collectionName = camelize(pluralize(identifierType)) + return `collections/${collectionName}/records`; +}; + +const options = findRecord('post', '1', { + resourcePath: dynamicPathFor('post'), +}) diff --git a/snippets/adapters/path-for-type/old.js b/snippets/adapters/path-for-type/old.js new file mode 100644 index 0000000..fdfcde7 --- /dev/null +++ b/snippets/adapters/path-for-type/old.js @@ -0,0 +1,6 @@ +export default class ApplicationAdapter extends JSONAPIAdapter { + pathForType(type) { + const collectionName = pluralize(camelize(type)); + return `collections/${collectionName}/records`; + } +} diff --git a/snippets/adapters/path-for-type/utils.js b/snippets/adapters/path-for-type/utils.js new file mode 100644 index 0000000..9616a23 --- /dev/null +++ b/snippets/adapters/path-for-type/utils.js @@ -0,0 +1,60 @@ +import { camelize } from '@ember/string'; +import { pluralize } from 'ember-inflector'; +import { recordIdentifierFor } from '@ember-data/store'; + +import { + findRecord as restFindRecord, + query as restQuery, + createRecord as restCreateRecord, + updateRecord as restUpdateRecord, + deleteRecord as restDeleteRecord, +} from '@ember-data/rest/request'; + +const _customResourcePath = (identifierType) => { + return `collections/${camelize(pluralize(identifierType))}/records`; +}; + +const findRecord = (identifierType, id, options) => { + options = { + ...options, + resourcePath: _customResourcePath(identifierType), + }; + return restFindRecord(identifierType, id, options); +}; + +const query = (identifierType, query, options) => { + options = { + ...options, + resourcePath: _customResourcePath(identifierType), + }; + return restQuery(identifierType, query, options); +}; + +const createRecord = (record, options) => { + const identifier = recordIdentifierFor(record); + options = { + ...options, + resourcePath: _customResourcePath(identifier.type), + }; + return restCreateRecord(record, options); +}; + +const updateRecord = (record, options) => { + const identifier = recordIdentifierFor(record); + options = { + ...options, + resourcePath: _customResourcePath(identifier.type), + }; + return restUpdateRecord(record, options); +}; + +const deleteRecord = (record, options) => { + const identifier = recordIdentifierFor(record); + options = { + ...options, + resourcePath: _customResourcePath(identifier.type), + }; + return restDeleteRecord(record, options); +}; + +export { findRecord, query, createRecord, updateRecord, deleteRecord }; diff --git a/translations/adapters/cache-lifetime/en-us.yaml b/translations/adapters/cache-lifetime/en-us.yaml new file mode 100644 index 0000000..944494e --- /dev/null +++ b/translations/adapters/cache-lifetime/en-us.yaml @@ -0,0 +1,4 @@ +title: Cache lifetime +description: > + In the past, cache lifetimes for single resources were controlled by either supplying the ''reload'' and ''backgroundReload'' options or by the Adapter's hooks for ''shouldReloadRecord'', ''shouldReloadAll'', ''shouldBackgroundReloadRecord'' and ''shouldBackgroundReloadAll''. + This behavior will now be controlled by the combination of either supplying ''cacheOptions'' on the associated ''RequestInfo'' or by supplying a ''lifetimes'' service to the ''Store''. diff --git a/translations/adapters/en-us.yaml b/translations/adapters/en-us.yaml new file mode 100644 index 0000000..d08d5fc --- /dev/null +++ b/translations/adapters/en-us.yaml @@ -0,0 +1 @@ +title: Adapters diff --git a/translations/adapters/general/en-us.yaml b/translations/adapters/general/en-us.yaml new file mode 100644 index 0000000..c30b858 --- /dev/null +++ b/translations/adapters/general/en-us.yaml @@ -0,0 +1,9 @@ +title: Adapters in general +description: | + In order to provide migration support for Adapter and Serializer, a ''LegacyNetworkHandler'' would be provided. This handler would take a request and convert it into the older form, calling the appropriate Adapter and Serializer methods. If no adapter exists for the type (including no application adapter), this handler would call ''next''. In this manner an app can incrementally migrate request-handling to this new paradigm on a per-type basis as desired. +
+ The package ''ember-data'' would automatically configure this handler. If not using ''ember-data'' this configuration would need to be done explicitly. +
+ We intend to support this handler through at least the 5.x series, not deprecating it's usage before 6.0. +
+ Similarly, the methods ''adapterFor'' and ''serializerFor'' will not be deprecated until at least 6.0; however, it should no longer be assumed that an application has an adapter or serializer at all. diff --git a/translations/adapters/host-and-namespace/en-us.yaml b/translations/adapters/host-and-namespace/en-us.yaml new file mode 100644 index 0000000..595d9c2 --- /dev/null +++ b/translations/adapters/host-and-namespace/en-us.yaml @@ -0,0 +1,5 @@ +title: Default Host and Namespace +description: > + To set default ''host'' and ''namespace'' for requests, you can use ''setBuildURLConfig'' method of ''@ember-data/request-utils'' package. +
+ This is module-level function, so you can do it anywhere in application, i.e. in your ''services/request-manager.js'' or ''services/store.js'' file. diff --git a/translations/adapters/path-for-type/en-us.yaml b/translations/adapters/path-for-type/en-us.yaml new file mode 100644 index 0000000..d38a1a1 --- /dev/null +++ b/translations/adapters/path-for-type/en-us.yaml @@ -0,0 +1,5 @@ +title: Use request builders instead of pathForType +description: > + To modify URL for request you can use ''resourcePath'' option for every request builder. Default configuration for ''JSON:API'', ''REST'', and ''ActiveRecord'' builders will be provided by EmberData. +
+ We recommend creating you own utility file with request builders that suites your backend needs. diff --git a/translations/en-us.yaml b/translations/en-us.yaml index b35e43f..a281f89 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -1,6 +1,6 @@ layout: application: - logo-alt-text: 'Ember Octane: the latest edition from Ember.js' + logo-alt-text: 'EmberData Request Service Cheat Sheet' title: EmberData Request Service Cheat Sheet description-1: > This guide is a cheat sheet for using 'EmberData's Request Service'. From 8f7f97cadce61eba9760ee5d2daa9781c6bc4ad7 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Wed, 4 Oct 2023 10:57:17 +0200 Subject: [PATCH 02/25] Update translations/adapters/path-for-type/en-us.yaml Co-authored-by: David Baker --- translations/adapters/path-for-type/en-us.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/adapters/path-for-type/en-us.yaml b/translations/adapters/path-for-type/en-us.yaml index d38a1a1..82c00c8 100644 --- a/translations/adapters/path-for-type/en-us.yaml +++ b/translations/adapters/path-for-type/en-us.yaml @@ -2,4 +2,4 @@ title: Use request builders instead of pathForType description: > To modify URL for request you can use ''resourcePath'' option for every request builder. Default configuration for ''JSON:API'', ''REST'', and ''ActiveRecord'' builders will be provided by EmberData.
- We recommend creating you own utility file with request builders that suites your backend needs. + We recommend creating your own utility file with request builders that suite your backend's needs. From aef6e575388bb1375a8d4aafda46f7cc0950f284 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Wed, 4 Oct 2023 10:57:41 +0200 Subject: [PATCH 03/25] Update translations/adapters/host-and-namespace/en-us.yaml Co-authored-by: David Baker --- translations/adapters/host-and-namespace/en-us.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/adapters/host-and-namespace/en-us.yaml b/translations/adapters/host-and-namespace/en-us.yaml index 595d9c2..e89daf3 100644 --- a/translations/adapters/host-and-namespace/en-us.yaml +++ b/translations/adapters/host-and-namespace/en-us.yaml @@ -1,5 +1,5 @@ title: Default Host and Namespace description: > - To set default ''host'' and ''namespace'' for requests, you can use ''setBuildURLConfig'' method of ''@ember-data/request-utils'' package. + To set the default ''host'' and ''namespace'' for requests, you can use the ''setBuildURLConfig'' method of ''@ember-data/request-utils'' package.
- This is module-level function, so you can do it anywhere in application, i.e. in your ''services/request-manager.js'' or ''services/store.js'' file. + This is a module-level function, so you can do it anywhere in your application, i.e. in your ''services/request-manager.js'' or ''services/store.js'' file. From 3f9bdbc422f8116a8455c336dbe74a4ea4750310 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Wed, 4 Oct 2023 10:57:46 +0200 Subject: [PATCH 04/25] Update translations/adapters/general/en-us.yaml Co-authored-by: David Baker --- translations/adapters/general/en-us.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/translations/adapters/general/en-us.yaml b/translations/adapters/general/en-us.yaml index c30b858..55a9bbe 100644 --- a/translations/adapters/general/en-us.yaml +++ b/translations/adapters/general/en-us.yaml @@ -1,9 +1,9 @@ title: Adapters in general description: | - In order to provide migration support for Adapter and Serializer, a ''LegacyNetworkHandler'' would be provided. This handler would take a request and convert it into the older form, calling the appropriate Adapter and Serializer methods. If no adapter exists for the type (including no application adapter), this handler would call ''next''. In this manner an app can incrementally migrate request-handling to this new paradigm on a per-type basis as desired. + In order to provide migration support for Adapters and Serializers, a ''LegacyNetworkHandler'' is provided. This handler takes a request and converts it into the older form, calling the appropriate Adapter and Serializer methods. If no adapter exists for the type (including no application adapter), this handler calls ''next''. In this manner an app can incrementally migrate request-handling to this new paradigm on a per-type basis as desired.
- The package ''ember-data'' would automatically configure this handler. If not using ''ember-data'' this configuration would need to be done explicitly. + The package ''ember-data'' automatically configures this handler. If not using the ''ember-data'' package, this configuration needs to be done explicitly.
- We intend to support this handler through at least the 5.x series, not deprecating it's usage before 6.0. + We intend to support this handler through at least the 5.x series -- not deprecating its usage before 6.0.
Similarly, the methods ''adapterFor'' and ''serializerFor'' will not be deprecated until at least 6.0; however, it should no longer be assumed that an application has an adapter or serializer at all. From ae763df6e0052d8e024615830eb9020fd0740e94 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Wed, 4 Oct 2023 10:57:54 +0200 Subject: [PATCH 05/25] Update translations/adapters/path-for-type/en-us.yaml Co-authored-by: David Baker --- translations/adapters/path-for-type/en-us.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/adapters/path-for-type/en-us.yaml b/translations/adapters/path-for-type/en-us.yaml index 82c00c8..f7060e0 100644 --- a/translations/adapters/path-for-type/en-us.yaml +++ b/translations/adapters/path-for-type/en-us.yaml @@ -1,5 +1,5 @@ title: Use request builders instead of pathForType description: > - To modify URL for request you can use ''resourcePath'' option for every request builder. Default configuration for ''JSON:API'', ''REST'', and ''ActiveRecord'' builders will be provided by EmberData. + To modify a URL for a request you can use the ''resourcePath'' option for every request builder. Default configuration for ''JSON:API'', ''REST'', and ''ActiveRecord'' builders will be provided by EmberData.
We recommend creating your own utility file with request builders that suite your backend's needs. From f31e5e4b0f24ebadfbad7696267102f0dfa22cf9 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 16 Oct 2023 13:37:29 +0200 Subject: [PATCH 06/25] Refactor adapter example for cache to map old one --- snippets/adapters/cache-lifetime/new.js | 39 ++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/snippets/adapters/cache-lifetime/new.js b/snippets/adapters/cache-lifetime/new.js index 2678c39..06cf526 100644 --- a/snippets/adapters/cache-lifetime/new.js +++ b/snippets/adapters/cache-lifetime/new.js @@ -4,9 +4,40 @@ import DataStore from '@ember-data/store'; export default class Store extends DataStore { constructor(args) { super(args); - this.lifetimes = new LifetimesService(this, { - apiCacheSoftExpires: 30_000, - apiCacheHardExpires: 60_000 - }); + // This is default configuration that would be set automatically be Ember Data + // this.lifetimes = new LifetimesService(this, { + // apiCacheSoftExpires: 30_000, + // apiCacheHardExpires: 60_000 + // }); + this.lifetimes = { + isHardExpired(identifier) { + const cached = this.store.cache.peekRequest(identifier); + const twentyMinutesInMs = 20 * 60 * 1000; + + function isStale(headers, expirationTime) { + const date = headers.get('date'); + + if (!date) { + return true; + } + + const time = new Date(date).getTime(); + const now = Date.now(); + const deadline = time + expirationTime; + + const result = now > deadline; + + return result; + } + + return !cached || !cached.response || isStale(cached.response.headers, twentyMinutesInMs); + }, + + isSoftExpired(identifier) { + const { downlink, effectiveType } = navigator.connection; + + return downlink > 0 && effectiveType === '4g'; + } + } } } From 56ea4712fad4b6953a99aae1db8d4740d381f64c Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 9 Oct 2023 15:26:44 +0200 Subject: [PATCH 07/25] Add findRecord section --- app/routes/application.js | 10 ++++++++++ snippets/fetching-data/find-record/new.js | 3 +++ snippets/fetching-data/find-record/old.js | 1 + .../fetching-data/find-record/own-builder.js | 17 +++++++++++++++++ .../find-record/replicate-store.js | 14 ++++++++++++++ translations/fetching-data/en-us.yaml | 1 + .../fetching-data/find-record/en-us.yaml | 3 +++ 7 files changed, 49 insertions(+) create mode 100644 snippets/fetching-data/find-record/new.js create mode 100644 snippets/fetching-data/find-record/old.js create mode 100644 snippets/fetching-data/find-record/own-builder.js create mode 100644 snippets/fetching-data/find-record/replicate-store.js create mode 100644 translations/fetching-data/en-us.yaml create mode 100644 translations/fetching-data/find-record/en-us.yaml diff --git a/app/routes/application.js b/app/routes/application.js index d4fac6a..c3b7f7f 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -3,6 +3,16 @@ import Route from '@ember/routing/route'; export default class ApplicationRoute extends Route { model() { return [ + { + id: 'fetching-data', + subsections: [ + { + id: 'find-record', + classicFiles: ['old.js'], + octaneFiles: ['new.js', 'replicate-store.js', 'own-builder.js'], + }, + ], + }, { id: 'adapters', subsections: [ diff --git a/snippets/fetching-data/find-record/new.js b/snippets/fetching-data/find-record/new.js new file mode 100644 index 0000000..35125d5 --- /dev/null +++ b/snippets/fetching-data/find-record/new.js @@ -0,0 +1,3 @@ +import { findRecord } from '@ember-data/json-api/request'; + +const { content: { data: user } } = await this.store.request(findRecord('user', '1')); diff --git a/snippets/fetching-data/find-record/old.js b/snippets/fetching-data/find-record/old.js new file mode 100644 index 0000000..73cb467 --- /dev/null +++ b/snippets/fetching-data/find-record/old.js @@ -0,0 +1 @@ +const user = await this.store.findRecord('user', '1'); diff --git a/snippets/fetching-data/find-record/own-builder.js b/snippets/fetching-data/find-record/own-builder.js new file mode 100644 index 0000000..e23da3a --- /dev/null +++ b/snippets/fetching-data/find-record/own-builder.js @@ -0,0 +1,17 @@ +// Bring your own builder +import { findRecord as edFindRecord } from '@ember-data/json-api/request'; + +async function findRecord(typeOrIdentifier, id, options) { + const result = await edFindRecord(typeOrIdentifier, id, options); + + return result.content.data; +} + +export default { + findRecord +}; + +// Somewhere in app +const user = await this.store.findRecord('user', '1'); + + diff --git a/snippets/fetching-data/find-record/replicate-store.js b/snippets/fetching-data/find-record/replicate-store.js new file mode 100644 index 0000000..00630b3 --- /dev/null +++ b/snippets/fetching-data/find-record/replicate-store.js @@ -0,0 +1,14 @@ +// Replicate old on store methods +import { findRecord } from '@ember-data/json-api/request'; +import DataStore from '@ember-data/store' + +export default class Store extends DataStore { + async findRecord(typeOrIdentifier, id, options) { + const result = await this.request(findRecord(typeOrIdentifier, id, options)); + // but you might want to return whole result object, as it has meta, errors, links, etc. + return result.content.data; + } +} + +// Somewhere in app +const user = await this.store.findRecord('user', '1') diff --git a/translations/fetching-data/en-us.yaml b/translations/fetching-data/en-us.yaml new file mode 100644 index 0000000..252c5e1 --- /dev/null +++ b/translations/fetching-data/en-us.yaml @@ -0,0 +1 @@ +title: Fetching Data diff --git a/translations/fetching-data/find-record/en-us.yaml b/translations/fetching-data/find-record/en-us.yaml new file mode 100644 index 0000000..7858eb4 --- /dev/null +++ b/translations/fetching-data/find-record/en-us.yaml @@ -0,0 +1,3 @@ +title: Find Record +description: | + Examples here are shown for apps that use JSON:API. Apps using other paradigms should use the builders for REST or ActiveRecord if applicable, or author their own (or a new community lib!) if not. From 7245f10a97cce29bfac5b1d320c2067ae115c5eb Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 9 Oct 2023 16:05:15 +0200 Subject: [PATCH 08/25] Add section about query/queryRecord --- app/routes/application.js | 15 +++++++++++++++ snippets/fetching-data/find-all/new.js | 4 ++++ snippets/fetching-data/find-all/old.js | 1 + snippets/fetching-data/query-record/new.js | 4 ++++ snippets/fetching-data/query-record/old.js | 1 + snippets/fetching-data/query/new.js | 4 ++++ snippets/fetching-data/query/old.js | 1 + translations/fetching-data/find-all/en-us.yaml | 3 +++ translations/fetching-data/find-record/en-us.yaml | 2 +- .../fetching-data/query-record/en-us.yaml | 2 ++ translations/fetching-data/query/en-us.yaml | 2 ++ 11 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 snippets/fetching-data/find-all/new.js create mode 100644 snippets/fetching-data/find-all/old.js create mode 100644 snippets/fetching-data/query-record/new.js create mode 100644 snippets/fetching-data/query-record/old.js create mode 100644 snippets/fetching-data/query/new.js create mode 100644 snippets/fetching-data/query/old.js create mode 100644 translations/fetching-data/find-all/en-us.yaml create mode 100644 translations/fetching-data/query-record/en-us.yaml create mode 100644 translations/fetching-data/query/en-us.yaml diff --git a/app/routes/application.js b/app/routes/application.js index c3b7f7f..9bce7b0 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -11,6 +11,21 @@ export default class ApplicationRoute extends Route { classicFiles: ['old.js'], octaneFiles: ['new.js', 'replicate-store.js', 'own-builder.js'], }, + { + id: 'find-all', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], + }, + { + id: 'query', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], + }, + { + id: 'query-record', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], + }, ], }, { diff --git a/snippets/fetching-data/find-all/new.js b/snippets/fetching-data/find-all/new.js new file mode 100644 index 0000000..eb85526 --- /dev/null +++ b/snippets/fetching-data/find-all/new.js @@ -0,0 +1,4 @@ +import { query } from '@ember-data/json-api/request'; + +const result = await store.request(query('user')); +const users = result.content.data; diff --git a/snippets/fetching-data/find-all/old.js b/snippets/fetching-data/find-all/old.js new file mode 100644 index 0000000..5170d13 --- /dev/null +++ b/snippets/fetching-data/find-all/old.js @@ -0,0 +1 @@ +const users = this.store.findAll('user') diff --git a/snippets/fetching-data/query-record/new.js b/snippets/fetching-data/query-record/new.js new file mode 100644 index 0000000..5d375ec --- /dev/null +++ b/snippets/fetching-data/query-record/new.js @@ -0,0 +1,4 @@ +import { query } from '@ember-data/json-api/request'; + +const result = await store.request(query('user', { ...params, limit: 1 })); +const user = result.content.data[0] ?? null; diff --git a/snippets/fetching-data/query-record/old.js b/snippets/fetching-data/query-record/old.js new file mode 100644 index 0000000..069f204 --- /dev/null +++ b/snippets/fetching-data/query-record/old.js @@ -0,0 +1 @@ +const user = await store.queryRecord('user', params); diff --git a/snippets/fetching-data/query/new.js b/snippets/fetching-data/query/new.js new file mode 100644 index 0000000..492698d --- /dev/null +++ b/snippets/fetching-data/query/new.js @@ -0,0 +1,4 @@ +import { query } from '@ember-data/json-api/request'; + +const result = await store.request(query('user', { filter: { name: 'John' } })); +const users = result.content.data; diff --git a/snippets/fetching-data/query/old.js b/snippets/fetching-data/query/old.js new file mode 100644 index 0000000..a9a9a45 --- /dev/null +++ b/snippets/fetching-data/query/old.js @@ -0,0 +1 @@ +const users = await store.query('user', { filter: { name: 'John' } }); diff --git a/translations/fetching-data/find-all/en-us.yaml b/translations/fetching-data/find-all/en-us.yaml new file mode 100644 index 0000000..0bc149b --- /dev/null +++ b/translations/fetching-data/find-all/en-us.yaml @@ -0,0 +1,3 @@ +title: findAll +description: | + There is no direct replacement for findAll, you should just use query without options instead. diff --git a/translations/fetching-data/find-record/en-us.yaml b/translations/fetching-data/find-record/en-us.yaml index 7858eb4..1c357ed 100644 --- a/translations/fetching-data/find-record/en-us.yaml +++ b/translations/fetching-data/find-record/en-us.yaml @@ -1,3 +1,3 @@ -title: Find Record +title: findRecord description: | Examples here are shown for apps that use JSON:API. Apps using other paradigms should use the builders for REST or ActiveRecord if applicable, or author their own (or a new community lib!) if not. diff --git a/translations/fetching-data/query-record/en-us.yaml b/translations/fetching-data/query-record/en-us.yaml new file mode 100644 index 0000000..2eee5b0 --- /dev/null +++ b/translations/fetching-data/query-record/en-us.yaml @@ -0,0 +1,2 @@ +title: queryRecord +description: There is no direct replacement of queryRecord. You can just use query with limit=1 option. diff --git a/translations/fetching-data/query/en-us.yaml b/translations/fetching-data/query/en-us.yaml new file mode 100644 index 0000000..98e9817 --- /dev/null +++ b/translations/fetching-data/query/en-us.yaml @@ -0,0 +1,2 @@ +title: query +description: The query just moved out from being on Store public interface to builders. From 6c17ce0588d179034d714a47e4c951442f81d4f8 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 16 Oct 2023 13:42:36 +0200 Subject: [PATCH 09/25] Fix review comments from Chris --- snippets/fetching-data/find-all/new.js | 4 +- snippets/fetching-data/find-record/new.js | 3 +- .../fetching-data/find-record/own-builder.js | 38 ++++++++++++++++--- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/snippets/fetching-data/find-all/new.js b/snippets/fetching-data/find-all/new.js index eb85526..3486b8d 100644 --- a/snippets/fetching-data/find-all/new.js +++ b/snippets/fetching-data/find-all/new.js @@ -1,4 +1,4 @@ import { query } from '@ember-data/json-api/request'; -const result = await store.request(query('user')); -const users = result.content.data; +await this.store.request(query('user')); +const users = this.store.peekAll('user') diff --git a/snippets/fetching-data/find-record/new.js b/snippets/fetching-data/find-record/new.js index 35125d5..f0fb988 100644 --- a/snippets/fetching-data/find-record/new.js +++ b/snippets/fetching-data/find-record/new.js @@ -1,3 +1,4 @@ import { findRecord } from '@ember-data/json-api/request'; -const { content: { data: user } } = await this.store.request(findRecord('user', '1')); +const result = await this.store.request(findRecord('user', '1')); +const user = result.content.data diff --git a/snippets/fetching-data/find-record/own-builder.js b/snippets/fetching-data/find-record/own-builder.js index e23da3a..83094cc 100644 --- a/snippets/fetching-data/find-record/own-builder.js +++ b/snippets/fetching-data/find-record/own-builder.js @@ -1,10 +1,32 @@ // Bring your own builder -import { findRecord as edFindRecord } from '@ember-data/json-api/request'; +import { buildBaseURL, buildQueryParams } from '@ember-data/request-utils' +import { pluralize } from 'ember-inflector'; -async function findRecord(typeOrIdentifier, id, options) { - const result = await edFindRecord(typeOrIdentifier, id, options); +async function findRecord(typeOrIdentifier, idOrOptions, maybeOptions) { + const identifier = typeof typeOrIdentifier === 'string' ? { type: typeOrIdentifier, id } : typeOrIdentifier; + const options = ((typeof typeOrIdentifier === 'string' ? maybeOptions : idOrOptions) || {}); + + const urlOptions = { + op: 'findRecord', + identifier, + resourcePath: pluralize(identifier.type), + }; + + const url = buildBaseURL(urlOptions); + const headers = new Headers(); + headers.append('Accept', 'application/vnd.api+json'); + headers.append('Content-Type', 'application/vnd.api+json'); + + return { + url: options.include?.length + ? `${url}?${buildQueryParams({ include: options.include }, options.urlParamsSettings)}` + : url, + method: 'GET', + headers, + op: 'findRecord', + records: [identifier], + }; - return result.content.data; } export default { @@ -12,6 +34,12 @@ export default { }; // Somewhere in app -const user = await this.store.findRecord('user', '1'); +const fetchOptions = findRecord('user', '1', { include: 'friends' }); +const result = await this.store.request(fetchOptions) +const user = result.content.data +// or using identifier for findRecord builder +const fetchOptions = findRecord({ type: 'user', id: '1' }, { include: 'friends' }); +const result = await this.store.request(fetchOptions) +const user = result.content.data From 298111f3f5ed8e4ee69ba50cf3bf23d5728f5e9c Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Fri, 20 Oct 2023 19:09:21 +0200 Subject: [PATCH 10/25] Change wording for find-all --- snippets/fetching-data/query/old.js | 2 +- translations/fetching-data/find-all/en-us.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/snippets/fetching-data/query/old.js b/snippets/fetching-data/query/old.js index a9a9a45..d85ba0e 100644 --- a/snippets/fetching-data/query/old.js +++ b/snippets/fetching-data/query/old.js @@ -1 +1 @@ -const users = await store.query('user', { filter: { name: 'John' } }); +const users = await this.store.query('user', { filter: { name: 'John' } }); diff --git a/translations/fetching-data/find-all/en-us.yaml b/translations/fetching-data/find-all/en-us.yaml index 0bc149b..749738e 100644 --- a/translations/fetching-data/find-all/en-us.yaml +++ b/translations/fetching-data/find-all/en-us.yaml @@ -1,3 +1,3 @@ title: findAll description: | - There is no direct replacement for findAll, you should just use query without options instead. + There is no direct replacement for findAll, you can use query without extra options instead. Here is how to achieve exact findAll behavior: From 22fbf241971007cf7936ceeee44e3b2760a9e300 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 9 Oct 2023 17:12:59 +0200 Subject: [PATCH 11/25] Add update section --- app/routes/application.js | 15 +++++++++++++++ snippets/updating-data/create-record/new.js | 13 +++++++++++++ snippets/updating-data/create-record/old.js | 2 ++ snippets/updating-data/save-record/new.js | 14 ++++++++++++++ snippets/updating-data/save-record/old.js | 4 ++++ .../updating-data/create-record/en-us.yaml | 2 ++ translations/updating-data/en-us.yaml | 7 +++++++ translations/updating-data/general/en-us.yaml | 7 +++++++ translations/updating-data/save-record/en-us.yaml | 2 ++ 9 files changed, 66 insertions(+) create mode 100644 snippets/updating-data/create-record/new.js create mode 100644 snippets/updating-data/create-record/old.js create mode 100644 snippets/updating-data/save-record/new.js create mode 100644 snippets/updating-data/save-record/old.js create mode 100644 translations/updating-data/create-record/en-us.yaml create mode 100644 translations/updating-data/en-us.yaml create mode 100644 translations/updating-data/general/en-us.yaml create mode 100644 translations/updating-data/save-record/en-us.yaml diff --git a/app/routes/application.js b/app/routes/application.js index 9bce7b0..8cf28a1 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -28,6 +28,21 @@ export default class ApplicationRoute extends Route { }, ], }, + { + id: 'updating-data', + subsections: [ + { + id: 'create-record', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], + }, + { + id: 'save-record', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], + }, + ], + }, { id: 'adapters', subsections: [ diff --git a/snippets/updating-data/create-record/new.js b/snippets/updating-data/create-record/new.js new file mode 100644 index 0000000..951f335 --- /dev/null +++ b/snippets/updating-data/create-record/new.js @@ -0,0 +1,13 @@ +import { recordIdentifierFor } from '@ember-data/store'; +import { createRecord, serializeResources } from '@ember-data/json-api/request'; + +const record = store.createRecord('user', {}); +const request = createRecord(record); +request.body = JSON.stringify( + serializeResources( + store.cache, + recordIdentifierFor(record) + ) +); + +await store.request(request); diff --git a/snippets/updating-data/create-record/old.js b/snippets/updating-data/create-record/old.js new file mode 100644 index 0000000..53a08e6 --- /dev/null +++ b/snippets/updating-data/create-record/old.js @@ -0,0 +1,2 @@ +const record = this.store.createRecord('user', { name: "Chris" }); +await record.save(); diff --git a/snippets/updating-data/save-record/new.js b/snippets/updating-data/save-record/new.js new file mode 100644 index 0000000..067545a --- /dev/null +++ b/snippets/updating-data/save-record/new.js @@ -0,0 +1,14 @@ +import { recordIdentifierFor } from '@ember-data/store'; +import { updateRecord, serializePatch } from '@ember-data/json-api/request'; + +user.name = 'Chris'; + +const request = updateRecord(user); +request.body = JSON.stringify( + serializePatch( + store.cache, + recordIdentifierFor(user) + ) +); + +await store.request(request); diff --git a/snippets/updating-data/save-record/old.js b/snippets/updating-data/save-record/old.js new file mode 100644 index 0000000..22cfe3f --- /dev/null +++ b/snippets/updating-data/save-record/old.js @@ -0,0 +1,4 @@ +const user = this.store.peekRecord('user', '1'); + +user.name = 'Chris'; +await user.save(); diff --git a/translations/updating-data/create-record/en-us.yaml b/translations/updating-data/create-record/en-us.yaml new file mode 100644 index 0000000..c03e200 --- /dev/null +++ b/translations/updating-data/create-record/en-us.yaml @@ -0,0 +1,2 @@ +title: createRecord +description: To create a new record using Ember Data you should use createRecord request and attach "body" to it. In case of JSON:API backend - you can user serializeResources request utility. diff --git a/translations/updating-data/en-us.yaml b/translations/updating-data/en-us.yaml new file mode 100644 index 0000000..705ef40 --- /dev/null +++ b/translations/updating-data/en-us.yaml @@ -0,0 +1,7 @@ +title: Creating/Updating Data +description: > + For requests that are expected to send a "body" to the API applications may choose to either serialize the body at the point of the request or to implement a Handler for the RequestManager to do so. + + EmberData does not provide a default handler which serializes because this is a unique concern for every app. However, EmberData does provide utilities on both the Cache and for some of the builders to make this easy. + + For JSON:API we show the "at point of request" approach using the utils provided by the @ember-data/json-api package here. diff --git a/translations/updating-data/general/en-us.yaml b/translations/updating-data/general/en-us.yaml new file mode 100644 index 0000000..472881c --- /dev/null +++ b/translations/updating-data/general/en-us.yaml @@ -0,0 +1,7 @@ +title: General concepts / philosophy +description: > + For requests that are expected to send a "body" to the API applications may choose to either serialize the body at the point of the request or to implement a Handler for the RequestManager to do so. + + Ember Data does not provide a default handler which serializes because this is a unique concern for every app. However, Ember Data does provide utilities on both the Cache and for some of the builders to make this easy. + + For JSON:API we show the "at point of request" approach using the utils provided by the @ember-data/json-api package here. diff --git a/translations/updating-data/save-record/en-us.yaml b/translations/updating-data/save-record/en-us.yaml new file mode 100644 index 0000000..744df44 --- /dev/null +++ b/translations/updating-data/save-record/en-us.yaml @@ -0,0 +1,2 @@ +title: saveRecord / updateRecord +description: To update record, you should send updateRecord request to your backend and attach "body" to it. Based on your backend server needs, you can use serializeResources or serializePatch request utilities. From 6a27c8196494449361ce737adad56fc69f77b40d Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 9 Oct 2023 17:31:44 +0200 Subject: [PATCH 12/25] Add small section about delete --- app/routes/application.js | 10 ++++++++++ snippets/deleting-data/delete-record/new.js | 6 ++++++ snippets/deleting-data/delete-record/old.js | 3 +++ translations/deleting-data/delete-record/en-us.yaml | 2 ++ translations/deleting-data/en-us.yaml | 1 + 5 files changed, 22 insertions(+) create mode 100644 snippets/deleting-data/delete-record/new.js create mode 100644 snippets/deleting-data/delete-record/old.js create mode 100644 translations/deleting-data/delete-record/en-us.yaml create mode 100644 translations/deleting-data/en-us.yaml diff --git a/app/routes/application.js b/app/routes/application.js index 8cf28a1..1f35f3e 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -43,6 +43,16 @@ export default class ApplicationRoute extends Route { }, ], }, + { + id: 'deleting-data', + subsections: [ + { + id: 'delete-record', + classicFiles: ['old.js'], + octaneFiles: ['new.js'], + }, + ], + }, { id: 'adapters', subsections: [ diff --git a/snippets/deleting-data/delete-record/new.js b/snippets/deleting-data/delete-record/new.js new file mode 100644 index 0000000..8c81e13 --- /dev/null +++ b/snippets/deleting-data/delete-record/new.js @@ -0,0 +1,6 @@ +import { deleteRecord } from '@ember-data/json-api/request'; + +const user = this.store.peekRecord('user', '1'); +this.store.deleteRecord(user); +await this.store.request(deleteRecord(user)); +this.store.unloadRecord(user); diff --git a/snippets/deleting-data/delete-record/old.js b/snippets/deleting-data/delete-record/old.js new file mode 100644 index 0000000..4d9a75c --- /dev/null +++ b/snippets/deleting-data/delete-record/old.js @@ -0,0 +1,3 @@ +const user = this.store.peekRecord('user', '1'); +user.deleteRecord(); +await user.save(); diff --git a/translations/deleting-data/delete-record/en-us.yaml b/translations/deleting-data/delete-record/en-us.yaml new file mode 100644 index 0000000..5e0a441 --- /dev/null +++ b/translations/deleting-data/delete-record/en-us.yaml @@ -0,0 +1,2 @@ +title: deleteRecord +description: To delete an existing record using Ember Data you should use deleteRecord builder to issue the request. diff --git a/translations/deleting-data/en-us.yaml b/translations/deleting-data/en-us.yaml new file mode 100644 index 0000000..74c6057 --- /dev/null +++ b/translations/deleting-data/en-us.yaml @@ -0,0 +1 @@ +title: Deleting data From 5b73aceb763dd369880a9dba65c8cb8eb4a136db Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 9 Oct 2023 17:33:12 +0200 Subject: [PATCH 13/25] Unify this.store --- snippets/fetching-data/query-record/new.js | 2 +- snippets/fetching-data/query-record/old.js | 2 +- snippets/fetching-data/query/new.js | 2 +- snippets/updating-data/create-record/new.js | 6 +++--- snippets/updating-data/save-record/new.js | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/snippets/fetching-data/query-record/new.js b/snippets/fetching-data/query-record/new.js index 5d375ec..00e9b0a 100644 --- a/snippets/fetching-data/query-record/new.js +++ b/snippets/fetching-data/query-record/new.js @@ -1,4 +1,4 @@ import { query } from '@ember-data/json-api/request'; -const result = await store.request(query('user', { ...params, limit: 1 })); +const result = await this.store.request(query('user', { ...params, limit: 1 })); const user = result.content.data[0] ?? null; diff --git a/snippets/fetching-data/query-record/old.js b/snippets/fetching-data/query-record/old.js index 069f204..715db76 100644 --- a/snippets/fetching-data/query-record/old.js +++ b/snippets/fetching-data/query-record/old.js @@ -1 +1 @@ -const user = await store.queryRecord('user', params); +const user = await this.store.queryRecord('user', params); diff --git a/snippets/fetching-data/query/new.js b/snippets/fetching-data/query/new.js index 492698d..95eeccf 100644 --- a/snippets/fetching-data/query/new.js +++ b/snippets/fetching-data/query/new.js @@ -1,4 +1,4 @@ import { query } from '@ember-data/json-api/request'; -const result = await store.request(query('user', { filter: { name: 'John' } })); +const result = await this.store.request(query('user', { filter: { name: 'John' } })); const users = result.content.data; diff --git a/snippets/updating-data/create-record/new.js b/snippets/updating-data/create-record/new.js index 951f335..2c7bcba 100644 --- a/snippets/updating-data/create-record/new.js +++ b/snippets/updating-data/create-record/new.js @@ -1,13 +1,13 @@ import { recordIdentifierFor } from '@ember-data/store'; import { createRecord, serializeResources } from '@ember-data/json-api/request'; -const record = store.createRecord('user', {}); +const record = this.store.createRecord('user', {}); const request = createRecord(record); request.body = JSON.stringify( serializeResources( - store.cache, + this.store.cache, recordIdentifierFor(record) ) ); -await store.request(request); +await this.store.request(request); diff --git a/snippets/updating-data/save-record/new.js b/snippets/updating-data/save-record/new.js index 067545a..2356431 100644 --- a/snippets/updating-data/save-record/new.js +++ b/snippets/updating-data/save-record/new.js @@ -6,9 +6,9 @@ user.name = 'Chris'; const request = updateRecord(user); request.body = JSON.stringify( serializePatch( - store.cache, + this.store.cache, recordIdentifierFor(user) ) ); -await store.request(request); +await this.store.request(request); From 0761911a389c133854b42d6405778783dae8f4b7 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 16 Oct 2023 13:44:38 +0200 Subject: [PATCH 14/25] Add handler example for create/update --- .../updating-data/create-record/handler.js | 50 +++++++++++++++++++ .../create-record/in-place-body.js | 14 ++++++ snippets/updating-data/save-record/handler.js | 50 +++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 snippets/updating-data/create-record/handler.js create mode 100644 snippets/updating-data/create-record/in-place-body.js create mode 100644 snippets/updating-data/save-record/handler.js diff --git a/snippets/updating-data/create-record/handler.js b/snippets/updating-data/create-record/handler.js new file mode 100644 index 0000000..2a77409 --- /dev/null +++ b/snippets/updating-data/create-record/handler.js @@ -0,0 +1,50 @@ +// Create handler for serialization of any record +import { recordIdentifierFor } from '@ember-data/store'; +import { serializeResources } from '@ember-data/json-api/request'; + +const updatesHandler = { + MUTATION_OPS = new Set(['createRecord', 'updateRecord']); + request(context, next) { + if (!MUTATION_OPS.has(context.request.op)) { + // Not a mutation, do nothing + return next(context.request); + } + + if (context.request.body) { + // body is already set, do nothing + return next(context.request); + } + + const { data, store } = context.request; + const newRequestParams = Object.assign({}, context.request, { + body: serializeResources( + store.cache, + recordIdentifierFor(data.record) + ) + }); + return next(newRequestParams); + } +} + +// Add this handler to your request manager +export default class extends RequestManager { + constructor(args) { + super(args); + this.use([updatesHandler, Fetch]); + this.useCache(CacheHandler); + } +} + +// then in your app just use createRecord builder and let handler care about serialization +import { createRecord } from '@ember-data/json-api/request'; + +const request = createRecord(record); +await this.store.request(request); + +// or overwrite body if you need to, handler will not touch it +import { createRecord } from '@ember-data/json-api/request'; + +const record = this.store.createRecord('feature', { name: "rest-enabled" }); +const request = createRecord(record); +// For some reason your endpoint for 'features' is not JSON:API compliant +request.body = JSON.stringify({ name: 'rest-enabled' }) diff --git a/snippets/updating-data/create-record/in-place-body.js b/snippets/updating-data/create-record/in-place-body.js new file mode 100644 index 0000000..706a667 --- /dev/null +++ b/snippets/updating-data/create-record/in-place-body.js @@ -0,0 +1,14 @@ +import { recordIdentifierFor } from '@ember-data/store'; +import { createRecord, serializeResources } from '@ember-data/json-api/request'; + +const record = this.store.createRecord('user', {}); +const request = createRecord(record); +// You can in place add body to request options +request.body = JSON.stringify( + serializeResources( + this.store.cache, + recordIdentifierFor(record) + ) +); + +await this.store.request(request); diff --git a/snippets/updating-data/save-record/handler.js b/snippets/updating-data/save-record/handler.js new file mode 100644 index 0000000..aa6bd2a --- /dev/null +++ b/snippets/updating-data/save-record/handler.js @@ -0,0 +1,50 @@ +// Create handler for serialization of any record +import { recordIdentifierFor } from '@ember-data/store'; +import { serializeResources } from '@ember-data/json-api/request'; + +const updatesHandler = { + MUTATION_OPS = new Set(['createRecord', 'updateRecord']); + request(context, next) { + if (!MUTATION_OPS.has(context.request.op)) { + // Not a mutation, do nothing + return next(context.request); + } + + if (context.request.body) { + // body is already set, do nothing + return next(context.request); + } + + const { data, store } = context.request; + const newRequestParams = Object.assign({}, context.request, { + body: serializeResources( + store.cache, + recordIdentifierFor(data.record) + ) + }); + return next(newRequestParams); + } +} + +// Add this handler to your request manager +export default class extends RequestManager { + constructor(args) { + super(args); + this.use([updatesHandler, Fetch]); + this.useCache(CacheHandler); + } +} + +// then in your app just use updateRecord builder and let handler care about serialization +import { updateRecord } from '@ember-data/json-api/request'; + +const request = updateRecord(record); +await this.store.request(request); + +// or overwrite body if you need to, handler will not touch it +import { updateRecord } from '@ember-data/rest/request'; + +const record = this.store.updateRecord('feature', { name: "rest-enabled" }); +const request = updateRecord(record); +// For some reason your endpoint for 'features' is not JSON:API compliant +request.body = JSON.stringify({ name: 'rest-enabled' }) From 9fffb131c57698e04a294f85fc76c6fdf7e70d88 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 16 Oct 2023 13:44:46 +0200 Subject: [PATCH 15/25] Add destroy record examples --- app/routes/application.js | 6 +++--- snippets/deleting-data/delete-record/destroy.js | 2 ++ snippets/deleting-data/delete-record/old.js | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 snippets/deleting-data/delete-record/destroy.js diff --git a/app/routes/application.js b/app/routes/application.js index 1f35f3e..d432306 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -34,12 +34,12 @@ export default class ApplicationRoute extends Route { { id: 'create-record', classicFiles: ['old.js'], - octaneFiles: ['new.js'], + octaneFiles: ['in-place-body.js', 'handler.js'], }, { id: 'save-record', classicFiles: ['old.js'], - octaneFiles: ['new.js'], + octaneFiles: ['new.js', 'handler.js'], }, ], }, @@ -48,7 +48,7 @@ export default class ApplicationRoute extends Route { subsections: [ { id: 'delete-record', - classicFiles: ['old.js'], + classicFiles: ['old.js', 'destroy.js'], octaneFiles: ['new.js'], }, ], diff --git a/snippets/deleting-data/delete-record/destroy.js b/snippets/deleting-data/delete-record/destroy.js new file mode 100644 index 0000000..75a6dd6 --- /dev/null +++ b/snippets/deleting-data/delete-record/destroy.js @@ -0,0 +1,2 @@ +const user = this.store.peekRecord('user', '1'); +await user.destroyRecord(); diff --git a/snippets/deleting-data/delete-record/old.js b/snippets/deleting-data/delete-record/old.js index 4d9a75c..4200db8 100644 --- a/snippets/deleting-data/delete-record/old.js +++ b/snippets/deleting-data/delete-record/old.js @@ -1,3 +1,4 @@ const user = this.store.peekRecord('user', '1'); user.deleteRecord(); await user.save(); +user.unloadRecord(); From bf28d8f01b558961d795cfdd5bd098d554a3dbbb Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Fri, 20 Oct 2023 19:11:01 +0200 Subject: [PATCH 16/25] Fix mutation_ops set --- snippets/updating-data/create-record/handler.js | 3 ++- snippets/updating-data/save-record/handler.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/snippets/updating-data/create-record/handler.js b/snippets/updating-data/create-record/handler.js index 2a77409..db5f32d 100644 --- a/snippets/updating-data/create-record/handler.js +++ b/snippets/updating-data/create-record/handler.js @@ -3,7 +3,8 @@ import { recordIdentifierFor } from '@ember-data/store'; import { serializeResources } from '@ember-data/json-api/request'; const updatesHandler = { - MUTATION_OPS = new Set(['createRecord', 'updateRecord']); + MUTATION_OPS: new Set(['createRecord', 'updateRecord']), + request(context, next) { if (!MUTATION_OPS.has(context.request.op)) { // Not a mutation, do nothing diff --git a/snippets/updating-data/save-record/handler.js b/snippets/updating-data/save-record/handler.js index aa6bd2a..03565af 100644 --- a/snippets/updating-data/save-record/handler.js +++ b/snippets/updating-data/save-record/handler.js @@ -3,7 +3,8 @@ import { recordIdentifierFor } from '@ember-data/store'; import { serializeResources } from '@ember-data/json-api/request'; const updatesHandler = { - MUTATION_OPS = new Set(['createRecord', 'updateRecord']); + MUTATION_OPS: new Set(['createRecord', 'updateRecord']), + request(context, next) { if (!MUTATION_OPS.has(context.request.op)) { // Not a mutation, do nothing From e325d5cdeb5c53c52541edd7fe4f07a6ffd0498d Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Thu, 4 Apr 2024 19:25:06 +0200 Subject: [PATCH 17/25] Move `setBuildURLConfig` according to guides (app.ts) --- translations/adapters/host-and-namespace/en-us.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/adapters/host-and-namespace/en-us.yaml b/translations/adapters/host-and-namespace/en-us.yaml index e89daf3..91ae170 100644 --- a/translations/adapters/host-and-namespace/en-us.yaml +++ b/translations/adapters/host-and-namespace/en-us.yaml @@ -2,4 +2,4 @@ title: Default Host and Namespace description: > To set the default ''host'' and ''namespace'' for requests, you can use the ''setBuildURLConfig'' method of ''@ember-data/request-utils'' package.
- This is a module-level function, so you can do it anywhere in your application, i.e. in your ''services/request-manager.js'' or ''services/store.js'' file. + This is a module-level function, so you can do it anywhere in your application theoretically, but we recommend doing it in the ''app/app.js'' file From 215ca9e5c08046d8f14d67a030854e42bc195fe8 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Thu, 4 Apr 2024 19:25:53 +0200 Subject: [PATCH 18/25] Use `ember-data/store` instead of `@ember-data/store` in the code examples --- snippets/adapters/cache-lifetime/new.js | 13 +++++++------ .../fetching-data/find-record/replicate-store.js | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/snippets/adapters/cache-lifetime/new.js b/snippets/adapters/cache-lifetime/new.js index 06cf526..c66a113 100644 --- a/snippets/adapters/cache-lifetime/new.js +++ b/snippets/adapters/cache-lifetime/new.js @@ -1,14 +1,15 @@ import { LifetimesService } from '@ember-data/request-utils'; -import DataStore from '@ember-data/store'; +import BaseStore from 'ember-data/store'; -export default class Store extends DataStore { +export default class Store extends BaseStore { constructor(args) { super(args); // This is default configuration that would be set automatically be Ember Data - // this.lifetimes = new LifetimesService(this, { - // apiCacheSoftExpires: 30_000, - // apiCacheHardExpires: 60_000 - // }); + this.lifetimes = new LifetimesService(this, { + apiCacheSoftExpires: 30_000, + apiCacheHardExpires: 60_000 + }); + // Or you can overwrite it with your own logic this.lifetimes = { isHardExpired(identifier) { const cached = this.store.cache.peekRequest(identifier); diff --git a/snippets/fetching-data/find-record/replicate-store.js b/snippets/fetching-data/find-record/replicate-store.js index 00630b3..11d0039 100644 --- a/snippets/fetching-data/find-record/replicate-store.js +++ b/snippets/fetching-data/find-record/replicate-store.js @@ -1,8 +1,8 @@ // Replicate old on store methods import { findRecord } from '@ember-data/json-api/request'; -import DataStore from '@ember-data/store' +import BaseStore from 'ember-data/store' -export default class Store extends DataStore { +export default class Store extends BaseStore { async findRecord(typeOrIdentifier, id, options) { const result = await this.request(findRecord(typeOrIdentifier, id, options)); // but you might want to return whole result object, as it has meta, errors, links, etc. @@ -11,4 +11,4 @@ export default class Store extends DataStore { } // Somewhere in app -const user = await this.store.findRecord('user', '1') +const user = await store.findRecord('user', '1') From b5dadbd44704daf66a6ca81c7f9483b15982b6a7 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Thu, 4 Apr 2024 19:26:06 +0200 Subject: [PATCH 19/25] Convert `this.store` to just `store` in the code examples --- snippets/deleting-data/delete-record/destroy.js | 2 +- snippets/deleting-data/delete-record/new.js | 8 ++++---- snippets/fetching-data/find-all/new.js | 6 ++++-- snippets/fetching-data/find-record/new.js | 2 +- snippets/fetching-data/find-record/own-builder.js | 4 ++-- snippets/fetching-data/query-record/new.js | 2 +- snippets/fetching-data/query/new.js | 2 +- snippets/updating-data/create-record/handler.js | 6 +++--- snippets/updating-data/create-record/in-place-body.js | 6 +++--- snippets/updating-data/create-record/new.js | 6 +++--- snippets/updating-data/save-record/handler.js | 6 +++--- snippets/updating-data/save-record/new.js | 4 ++-- 12 files changed, 28 insertions(+), 26 deletions(-) diff --git a/snippets/deleting-data/delete-record/destroy.js b/snippets/deleting-data/delete-record/destroy.js index 75a6dd6..d5fcf44 100644 --- a/snippets/deleting-data/delete-record/destroy.js +++ b/snippets/deleting-data/delete-record/destroy.js @@ -1,2 +1,2 @@ -const user = this.store.peekRecord('user', '1'); +const user = store.peekRecord('user', '1'); await user.destroyRecord(); diff --git a/snippets/deleting-data/delete-record/new.js b/snippets/deleting-data/delete-record/new.js index 8c81e13..8dc1fea 100644 --- a/snippets/deleting-data/delete-record/new.js +++ b/snippets/deleting-data/delete-record/new.js @@ -1,6 +1,6 @@ import { deleteRecord } from '@ember-data/json-api/request'; -const user = this.store.peekRecord('user', '1'); -this.store.deleteRecord(user); -await this.store.request(deleteRecord(user)); -this.store.unloadRecord(user); +const user = store.peekRecord('user', '1'); +store.deleteRecord(user); +await store.request(deleteRecord(user)); +store.unloadRecord(user); diff --git a/snippets/fetching-data/find-all/new.js b/snippets/fetching-data/find-all/new.js index 3486b8d..b7ace05 100644 --- a/snippets/fetching-data/find-all/new.js +++ b/snippets/fetching-data/find-all/new.js @@ -1,4 +1,6 @@ import { query } from '@ember-data/json-api/request'; -await this.store.request(query('user')); -const users = this.store.peekAll('user') +const result = await store.request(query('user')); +const users = result.content.data; +// or +const users = store.peekAll('user') diff --git a/snippets/fetching-data/find-record/new.js b/snippets/fetching-data/find-record/new.js index f0fb988..01a97f4 100644 --- a/snippets/fetching-data/find-record/new.js +++ b/snippets/fetching-data/find-record/new.js @@ -1,4 +1,4 @@ import { findRecord } from '@ember-data/json-api/request'; -const result = await this.store.request(findRecord('user', '1')); +const result = await store.request(findRecord('user', '1')); const user = result.content.data diff --git a/snippets/fetching-data/find-record/own-builder.js b/snippets/fetching-data/find-record/own-builder.js index 83094cc..8799d28 100644 --- a/snippets/fetching-data/find-record/own-builder.js +++ b/snippets/fetching-data/find-record/own-builder.js @@ -35,11 +35,11 @@ export default { // Somewhere in app const fetchOptions = findRecord('user', '1', { include: 'friends' }); -const result = await this.store.request(fetchOptions) +const result = await store.request(fetchOptions) const user = result.content.data // or using identifier for findRecord builder const fetchOptions = findRecord({ type: 'user', id: '1' }, { include: 'friends' }); -const result = await this.store.request(fetchOptions) +const result = await store.request(fetchOptions) const user = result.content.data diff --git a/snippets/fetching-data/query-record/new.js b/snippets/fetching-data/query-record/new.js index 00e9b0a..5d375ec 100644 --- a/snippets/fetching-data/query-record/new.js +++ b/snippets/fetching-data/query-record/new.js @@ -1,4 +1,4 @@ import { query } from '@ember-data/json-api/request'; -const result = await this.store.request(query('user', { ...params, limit: 1 })); +const result = await store.request(query('user', { ...params, limit: 1 })); const user = result.content.data[0] ?? null; diff --git a/snippets/fetching-data/query/new.js b/snippets/fetching-data/query/new.js index 95eeccf..492698d 100644 --- a/snippets/fetching-data/query/new.js +++ b/snippets/fetching-data/query/new.js @@ -1,4 +1,4 @@ import { query } from '@ember-data/json-api/request'; -const result = await this.store.request(query('user', { filter: { name: 'John' } })); +const result = await store.request(query('user', { filter: { name: 'John' } })); const users = result.content.data; diff --git a/snippets/updating-data/create-record/handler.js b/snippets/updating-data/create-record/handler.js index db5f32d..0985495 100644 --- a/snippets/updating-data/create-record/handler.js +++ b/snippets/updating-data/create-record/handler.js @@ -39,13 +39,13 @@ export default class extends RequestManager { // then in your app just use createRecord builder and let handler care about serialization import { createRecord } from '@ember-data/json-api/request'; -const request = createRecord(record); -await this.store.request(request); +const requestObj = createRecord(record); +await store.request(requestObj); // or overwrite body if you need to, handler will not touch it import { createRecord } from '@ember-data/json-api/request'; -const record = this.store.createRecord('feature', { name: "rest-enabled" }); +const record = store.createRecord('feature', { name: "rest-enabled" }); const request = createRecord(record); // For some reason your endpoint for 'features' is not JSON:API compliant request.body = JSON.stringify({ name: 'rest-enabled' }) diff --git a/snippets/updating-data/create-record/in-place-body.js b/snippets/updating-data/create-record/in-place-body.js index 706a667..2abd488 100644 --- a/snippets/updating-data/create-record/in-place-body.js +++ b/snippets/updating-data/create-record/in-place-body.js @@ -1,14 +1,14 @@ import { recordIdentifierFor } from '@ember-data/store'; import { createRecord, serializeResources } from '@ember-data/json-api/request'; -const record = this.store.createRecord('user', {}); +const record = store.createRecord('user', {}); const request = createRecord(record); // You can in place add body to request options request.body = JSON.stringify( serializeResources( - this.store.cache, + store.cache, recordIdentifierFor(record) ) ); -await this.store.request(request); +await store.request(request); diff --git a/snippets/updating-data/create-record/new.js b/snippets/updating-data/create-record/new.js index 2c7bcba..951f335 100644 --- a/snippets/updating-data/create-record/new.js +++ b/snippets/updating-data/create-record/new.js @@ -1,13 +1,13 @@ import { recordIdentifierFor } from '@ember-data/store'; import { createRecord, serializeResources } from '@ember-data/json-api/request'; -const record = this.store.createRecord('user', {}); +const record = store.createRecord('user', {}); const request = createRecord(record); request.body = JSON.stringify( serializeResources( - this.store.cache, + store.cache, recordIdentifierFor(record) ) ); -await this.store.request(request); +await store.request(request); diff --git a/snippets/updating-data/save-record/handler.js b/snippets/updating-data/save-record/handler.js index 03565af..65ab8b1 100644 --- a/snippets/updating-data/save-record/handler.js +++ b/snippets/updating-data/save-record/handler.js @@ -39,13 +39,13 @@ export default class extends RequestManager { // then in your app just use updateRecord builder and let handler care about serialization import { updateRecord } from '@ember-data/json-api/request'; -const request = updateRecord(record); -await this.store.request(request); +const requestObj = updateRecord(record); +await store.request(requestObj); // or overwrite body if you need to, handler will not touch it import { updateRecord } from '@ember-data/rest/request'; -const record = this.store.updateRecord('feature', { name: "rest-enabled" }); +const record = store.updateRecord('feature', { name: "rest-enabled" }); const request = updateRecord(record); // For some reason your endpoint for 'features' is not JSON:API compliant request.body = JSON.stringify({ name: 'rest-enabled' }) diff --git a/snippets/updating-data/save-record/new.js b/snippets/updating-data/save-record/new.js index 2356431..067545a 100644 --- a/snippets/updating-data/save-record/new.js +++ b/snippets/updating-data/save-record/new.js @@ -6,9 +6,9 @@ user.name = 'Chris'; const request = updateRecord(user); request.body = JSON.stringify( serializePatch( - this.store.cache, + store.cache, recordIdentifierFor(user) ) ); -await this.store.request(request); +await store.request(request); From 4c04c480ab9943cffa68c7247b498f0b5a8d54bc Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Thu, 4 Apr 2024 19:50:07 +0200 Subject: [PATCH 20/25] Clean up sections with text only --- app/components/guide-section/subsection.hbs | 126 ++++++++++---------- 1 file changed, 60 insertions(+), 66 deletions(-) diff --git a/app/components/guide-section/subsection.hbs b/app/components/guide-section/subsection.hbs index 484c5f4..ea77d72 100644 --- a/app/components/guide-section/subsection.hbs +++ b/app/components/guide-section/subsection.hbs @@ -1,101 +1,95 @@ {{#let @subsection.id - (t (concat @sectionId "." @subsection.id ".title")) - (t (concat @sectionId "." @subsection.id ".description") htmlSafe=true) + (t (concat @sectionId '.' @subsection.id '.title')) + (t (concat @sectionId '.' @subsection.id '.description') htmlSafe=true) as |subsectionId subsectionTitle subsectionDescription| }}
-
+ -

+

{{subsectionDescription}}

-
-

- {{t "component.guide-section.subsection.classic"}} -

+ {{#if @subsection.classicFiles.length}} +
+

+ {{t 'component.guide-section.subsection.classic'}} +

- {{#each @subsection.classicFiles as |file|}} -
- -
- {{/each}} + {{#each @subsection.classicFiles as |file|}} +
+ +
+ {{/each}} - {{#if @subsection.classicDescriptionKey}} -

- {{t @subsection.classicDescriptionKey htmlSafe=true}} -

- {{/if}} -
+ {{#if @subsection.classicDescriptionKey}} +

+ {{t @subsection.classicDescriptionKey htmlSafe=true}} +

+ {{/if}} +
-
-

- {{t "component.guide-section.subsection.octane"}} -

+ {{/if}} + + {{#if @subsection.octaneFiles.length}} +
+

+ {{t 'component.guide-section.subsection.octane'}} +

- {{#each @subsection.octaneFiles as |file|}} -
- -
- {{/each}} + {{#each @subsection.octaneFiles as |file|}} +
+ +
+ {{/each}} - {{#if @subsection.octaneDescriptionKey}} -

- {{t @subsection.octaneDescriptionKey htmlSafe=true}} -

- {{/if}} -
+ {{#if @subsection.octaneDescriptionKey}} +

+ {{t @subsection.octaneDescriptionKey htmlSafe=true}} +

+ {{/if}} +
+ {{/if}}
-
-{{/let}} +{{/let}} \ No newline at end of file From 83afdadfc9c960efc0b2b2ca364a66525439354b Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Thu, 4 Apr 2024 19:50:41 +0200 Subject: [PATCH 21/25] Don't mention cache in requestManager --- snippets/updating-data/create-record/handler.js | 1 - snippets/updating-data/save-record/handler.js | 1 - 2 files changed, 2 deletions(-) diff --git a/snippets/updating-data/create-record/handler.js b/snippets/updating-data/create-record/handler.js index 0985495..7e5e0f6 100644 --- a/snippets/updating-data/create-record/handler.js +++ b/snippets/updating-data/create-record/handler.js @@ -32,7 +32,6 @@ export default class extends RequestManager { constructor(args) { super(args); this.use([updatesHandler, Fetch]); - this.useCache(CacheHandler); } } diff --git a/snippets/updating-data/save-record/handler.js b/snippets/updating-data/save-record/handler.js index 65ab8b1..125b343 100644 --- a/snippets/updating-data/save-record/handler.js +++ b/snippets/updating-data/save-record/handler.js @@ -32,7 +32,6 @@ export default class extends RequestManager { constructor(args) { super(args); this.use([updatesHandler, Fetch]); - this.useCache(CacheHandler); } } From 4524c1ebcae63f55f676683c35c4cfd1c679e748 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Thu, 4 Apr 2024 19:51:01 +0200 Subject: [PATCH 22/25] Better example for making own builders --- snippets/adapters/path-for-type/utils.js | 70 +++++++++--------------- snippets/fetching-data/find-all/new.js | 4 +- 2 files changed, 26 insertions(+), 48 deletions(-) diff --git a/snippets/adapters/path-for-type/utils.js b/snippets/adapters/path-for-type/utils.js index 9616a23..a12458f 100644 --- a/snippets/adapters/path-for-type/utils.js +++ b/snippets/adapters/path-for-type/utils.js @@ -1,60 +1,40 @@ +// utils/my-backend-request-builders.js import { camelize } from '@ember/string'; import { pluralize } from 'ember-inflector'; -import { recordIdentifierFor } from '@ember-data/store'; - -import { - findRecord as restFindRecord, - query as restQuery, - createRecord as restCreateRecord, - updateRecord as restUpdateRecord, - deleteRecord as restDeleteRecord, -} from '@ember-data/rest/request'; +import { buildBaseURL, buildQueryParams } from '@ember-data/request-utils'; const _customResourcePath = (identifierType) => { return `collections/${camelize(pluralize(identifierType))}/records`; }; -const findRecord = (identifierType, id, options) => { - options = { - ...options, - resourcePath: _customResourcePath(identifierType), - }; - return restFindRecord(identifierType, id, options); -}; - -const query = (identifierType, query, options) => { - options = { - ...options, - resourcePath: _customResourcePath(identifierType), - }; - return restQuery(identifierType, query, options); -}; +async function findRecord(typeOrIdentifier, idOrOptions, maybeOptions) { + const identifier = typeof typeOrIdentifier === 'string' ? { type: typeOrIdentifier, id } : typeOrIdentifier; + const options = ((typeof typeOrIdentifier === 'string' ? maybeOptions : idOrOptions) || {}); -const createRecord = (record, options) => { - const identifier = recordIdentifierFor(record); - options = { - ...options, + const urlOptions = { + op: 'findRecord', + identifier, resourcePath: _customResourcePath(identifier.type), }; - return restCreateRecord(record, options); -}; -const updateRecord = (record, options) => { - const identifier = recordIdentifierFor(record); - options = { - ...options, - resourcePath: _customResourcePath(identifier.type), + const url = buildBaseURL(urlOptions); + const headers = new Headers(); + headers.append('Accept', 'application/vnd.api+json'); + headers.append('Content-Type', 'application/vnd.api+json'); + + return { + url: options.include?.length + ? `${url}?${buildQueryParams({ include: options.include }, options.urlParamsSettings)}` + : url, + method: 'GET', + headers, + op: 'findRecord', + records: [identifier], }; - return restUpdateRecord(record, options); -}; -const deleteRecord = (record, options) => { - const identifier = recordIdentifierFor(record); - options = { - ...options, - resourcePath: _customResourcePath(identifier.type), - }; - return restDeleteRecord(record, options); +} + +export default { + findRecord }; -export { findRecord, query, createRecord, updateRecord, deleteRecord }; diff --git a/snippets/fetching-data/find-all/new.js b/snippets/fetching-data/find-all/new.js index b7ace05..528e25c 100644 --- a/snippets/fetching-data/find-all/new.js +++ b/snippets/fetching-data/find-all/new.js @@ -1,6 +1,4 @@ import { query } from '@ember-data/json-api/request'; -const result = await store.request(query('user')); -const users = result.content.data; -// or +await store.request(query('user')); const users = store.peekAll('user') From aa605bbe84ec58fc82a1b436bdbf1b213473a283 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Thu, 2 May 2024 22:46:24 +0200 Subject: [PATCH 23/25] Serializers section --- app/routes/application.js | 10 +++ snippets/serializers/general/app-code.js | 23 +++++++ snippets/serializers/general/handler.js | 13 ++++ snippets/serializers/general/old.js | 36 +++++++++++ snippets/serializers/general/utils.js | 69 +++++++++++++++++++++ translations/serializers/en-us.yaml | 1 + translations/serializers/general/en-us.yaml | 15 +++++ 7 files changed, 167 insertions(+) create mode 100644 snippets/serializers/general/app-code.js create mode 100644 snippets/serializers/general/handler.js create mode 100644 snippets/serializers/general/old.js create mode 100644 snippets/serializers/general/utils.js create mode 100644 translations/serializers/en-us.yaml create mode 100644 translations/serializers/general/en-us.yaml diff --git a/app/routes/application.js b/app/routes/application.js index d432306..6c602f4 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -76,6 +76,16 @@ export default class ApplicationRoute extends Route { }, ], }, + { + id: 'serializers', + subsections: [ + { + id: 'general', + classicFiles: ['old.js'], + octaneFiles: ['utils.js', 'app-code.js', 'handler.js'], + }, + ], + }, ]; } } diff --git a/snippets/serializers/general/app-code.js b/snippets/serializers/general/app-code.js new file mode 100644 index 0000000..19346bc --- /dev/null +++ b/snippets/serializers/general/app-code.js @@ -0,0 +1,23 @@ +// File: my-app/components/my-component.js + +import { serializeResource } from "my-app/utils/serialization"; +import { recordIdentifierFor } from '@ember-data/store'; +import { createRecord } from "@ember-data/json-api/request" + +// ... +let record = store.createRecord('blog-post', { + title: "My first blog post", + body: "This is the body of my blog post", + createdAt: new Date(), + user: currentUser, +}); + +const request = createRecord(record); +request.body = JSON.stringify( + serializeResource( + store.cache, + recordIdentifierFor(record) + ) +); + +await this.store.request(request); diff --git a/snippets/serializers/general/handler.js b/snippets/serializers/general/handler.js new file mode 100644 index 0000000..b32498d --- /dev/null +++ b/snippets/serializers/general/handler.js @@ -0,0 +1,13 @@ +// File: my-app/utils/my-custom-rest-handler.js + +import { normalizeResource } from 'my-app/utils/serialization'; + +const MyCustomRESTHandler = { + async request(context, next) { + const { content, request } = await next(context.request); + // convert to JSON:API, because EmberData expect it by default (using the JSON:API Cache) + return normalizeResource(content, request.store.schema); + }, +}; + +export default MyCustomRESTHandler; diff --git a/snippets/serializers/general/old.js b/snippets/serializers/general/old.js new file mode 100644 index 0000000..9d177af --- /dev/null +++ b/snippets/serializers/general/old.js @@ -0,0 +1,36 @@ +import JSONAPISerializer from '@ember-data/serializer/json-api'; + +export default class ApplicationSerializer extends JSONAPISerializer { + primaryKey = '_id'; + + keyForAttribute(attr) { + return attr.replace(/_/g, '-'); // blog_post_comment becomes blog-post-comment + } + + keyForRelationship(key, relationship) { + return key + 'Ids'; + } + + serialize(snapshot, options) { + let json = super.serialize(...arguments); + + json.data.attributes.cost = { + amount: json.data.attributes.amount, + currency: json.data.attributes.currency, + }; + + delete json.data.attributes.amount; + delete json.data.attributes.currency; + + return json; + } + + normalizeResponse(store, primaryModelClass, payload, id, requestType) { + payload.data.attributes.amount = payload.data.attributes.cost.amount; + payload.data.attributes.currency = payload.data.attributes.cost.currency; + + delete payload.data.attributes.cost; + + return super.normalizeResponse(...arguments); + } +} diff --git a/snippets/serializers/general/utils.js b/snippets/serializers/general/utils.js new file mode 100644 index 0000000..e7cb6c8 --- /dev/null +++ b/snippets/serializers/general/utils.js @@ -0,0 +1,69 @@ +// File: my-app/utils/serialization.js + +const keyForAttribute = (attr) => { + return attr.replace(/_/g, '-'); // blog_post_comment becomes blog-post-comment +} + +const keyForRelationship = (relationship) => { + return relationship + 'Ids'; +} + +const serializeResource = (cache, identifier) => { + const body = { _id: identifier.id, type: identifier.type }; + + if (cache.hasChangedAttrs(identifier)) { + const attrsChanges = cache.changedAttrs(identifier); + + Object.keys(attrsChanges).forEach((attr) => { + const change = attrsChanges[attr][1]; + body[keyForAttribute(attr)] = change === undefined ? null : change; + }); + } + + if (cache.hasChangedRelationships(identifier)) { + const relationshipsChanged = cache.changedRelationships(identifier); + if (relationshipsChanged.size) { + relationshipsChanged.forEach((diff, key) => { + body[keyForRelationship(key)] = diff.localState.id; + }); + } + } + + return body; +}; + +const normalizeResource = (item, schema) => { + // destructuring and renaming primaryKey from _id to id + const { _id: id, type } = item; + + const attributesDefined = schema.attributesDefinitionFor({ type }); + const relationshipsDefined = schema.relationshipsDefinitionFor({ type }); + + const data = { id, type, attributes: {} }; + + for (const attr of Object.values(attributesDefined)) { + data.attributes[attr.name] = item[attr.name]; + } + + data.attributes.amount = item.data.attributes.cost.amount; + data.attributes.currency = item.data.attributes.cost.currency; + + for (const rel of Object.values(relationshipsDefined)) { + if (Object.hasOwnProperty.call(item, rel.name)) { + data.relationships ??= {}; + data.relationships[rel.name] = { + data: { + id: item[rel.name], + type: rel.type, + }, + }; + } + } + + return data; +}; + +export { + normalizeResource, + serializeResource, +} diff --git a/translations/serializers/en-us.yaml b/translations/serializers/en-us.yaml new file mode 100644 index 0000000..df651e9 --- /dev/null +++ b/translations/serializers/en-us.yaml @@ -0,0 +1 @@ +title: Serializers diff --git a/translations/serializers/general/en-us.yaml b/translations/serializers/general/en-us.yaml new file mode 100644 index 0000000..5fb8ef9 --- /dev/null +++ b/translations/serializers/general/en-us.yaml @@ -0,0 +1,15 @@ +title: Serializers in general +description: | + In order to provide migration support for Adapters and Serializers, a ''LegacyNetworkHandler'' is provided. This handler takes a request and converts it into the older form, calling the appropriate Adapter and Serializer methods. If no serializer exists for the type (including no application serializer), this handler calls ''next''. In this manner an app can incrementally migrate request-handling to this new paradigm on a per-type basis as desired. +
+ The package ''ember-data'' automatically configures this handler. If not using the ''ember-data'' package, this configuration needs to be done explicitly. +
+ We intend to support this handler through at least the 5.x series -- not deprecating its usage before 6.0. +
+ Similarly, the methods ''adapterFor'' and ''serializerFor'' will not be deprecated until at least 6.0; however, it should no longer be assumed that an application has an adapter or serializer at all. +
+ Serializers previously was used to accomplish two main things: serialization and normalization. +
+ Normalization is now done in handlers, before returning the response. +
+ Serialization can be done in handler, but we encourage application developers to do it in request builder. From 51f219de0a4eda97a4dd8e971cccd79e2ead3224 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Fri, 10 May 2024 13:01:21 +0200 Subject: [PATCH 24/25] Update findRecord with type declarations and own builders --- app/routes/application.js | 4 +-- snippets/fetching-data/find-record/new.ts | 5 ++++ snippets/fetching-data/find-record/old.ts | 3 ++ .../{own-builder.js => own-builder.ts} | 29 ++++++++++--------- .../fetching-data/find-all/en-us.yaml | 1 + translations/serializers/general/en-us.yaml | 2 +- 6 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 snippets/fetching-data/find-record/new.ts create mode 100644 snippets/fetching-data/find-record/old.ts rename snippets/fetching-data/find-record/{own-builder.js => own-builder.ts} (51%) diff --git a/app/routes/application.js b/app/routes/application.js index 6c602f4..4b9f63f 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -8,8 +8,8 @@ export default class ApplicationRoute extends Route { subsections: [ { id: 'find-record', - classicFiles: ['old.js'], - octaneFiles: ['new.js', 'replicate-store.js', 'own-builder.js'], + classicFiles: ['old.js', 'old.ts'], + octaneFiles: ['new.js', 'new.ts', 'own-builder.ts'], }, { id: 'find-all', diff --git a/snippets/fetching-data/find-record/new.ts b/snippets/fetching-data/find-record/new.ts new file mode 100644 index 0000000..f3492a5 --- /dev/null +++ b/snippets/fetching-data/find-record/new.ts @@ -0,0 +1,5 @@ +import { findRecord } from '@ember-data/json-api/request'; +import type { User } from 'my-app/models/user'; + +const { content } = await store.request(findRecord('user', '1')); +const user = content.data diff --git a/snippets/fetching-data/find-record/old.ts b/snippets/fetching-data/find-record/old.ts new file mode 100644 index 0000000..45a6ce3 --- /dev/null +++ b/snippets/fetching-data/find-record/old.ts @@ -0,0 +1,3 @@ +import type User from "my-app/models/user"; + +const user = await this.store.findRecord('user', '1'); diff --git a/snippets/fetching-data/find-record/own-builder.js b/snippets/fetching-data/find-record/own-builder.ts similarity index 51% rename from snippets/fetching-data/find-record/own-builder.js rename to snippets/fetching-data/find-record/own-builder.ts index 8799d28..bae77f1 100644 --- a/snippets/fetching-data/find-record/own-builder.js +++ b/snippets/fetching-data/find-record/own-builder.ts @@ -1,10 +1,21 @@ // Bring your own builder import { buildBaseURL, buildQueryParams } from '@ember-data/request-utils' import { pluralize } from 'ember-inflector'; +import type { RequestSignature } from '@warp-drive/core-types/symbols'; +import type { TypeFromInstance } from '@warp-drive/core-types/record'; +import type { FindRecordOptions } from '@warp-drive/core-types/request'; + +type MyRequest = { + url: string + method: 'GET' + headers: Headers + op: 'findRecord' + records: Array<{ type: TypeFromInstance, id: string }>; + [RequestSignature]: Type +} -async function findRecord(typeOrIdentifier, idOrOptions, maybeOptions) { - const identifier = typeof typeOrIdentifier === 'string' ? { type: typeOrIdentifier, id } : typeOrIdentifier; - const options = ((typeof typeOrIdentifier === 'string' ? maybeOptions : idOrOptions) || {}); +function findRecord(type: TypeFromInstance, id: string, options: FindRecordOptions): MyRequest { + const identifier = { type, id }; const urlOptions = { op: 'findRecord', @@ -17,7 +28,7 @@ async function findRecord(typeOrIdentifier, idOrOptions, maybeOptions) { headers.append('Accept', 'application/vnd.api+json'); headers.append('Content-Type', 'application/vnd.api+json'); - return { + const result = { url: options.include?.length ? `${url}?${buildQueryParams({ include: options.include }, options.urlParamsSettings)}` : url, @@ -27,19 +38,11 @@ async function findRecord(typeOrIdentifier, idOrOptions, maybeOptions) { records: [identifier], }; + return result as MyRequest; } export default { findRecord }; -// Somewhere in app -const fetchOptions = findRecord('user', '1', { include: 'friends' }); -const result = await store.request(fetchOptions) -const user = result.content.data -// or using identifier for findRecord builder -const fetchOptions = findRecord({ type: 'user', id: '1' }, { include: 'friends' }); -const result = await store.request(fetchOptions) -const user = result.content.data - diff --git a/translations/fetching-data/find-all/en-us.yaml b/translations/fetching-data/find-all/en-us.yaml index 749738e..f47481b 100644 --- a/translations/fetching-data/find-all/en-us.yaml +++ b/translations/fetching-data/find-all/en-us.yaml @@ -1,3 +1,4 @@ title: findAll description: | There is no direct replacement for findAll, you can use query without extra options instead. Here is how to achieve exact findAll behavior: + We discourage using peekAll. When you made a request, you need to guarantee that everything you requested is part of response you get back. diff --git a/translations/serializers/general/en-us.yaml b/translations/serializers/general/en-us.yaml index 5fb8ef9..36a7f4b 100644 --- a/translations/serializers/general/en-us.yaml +++ b/translations/serializers/general/en-us.yaml @@ -1,6 +1,6 @@ title: Serializers in general description: | - In order to provide migration support for Adapters and Serializers, a ''LegacyNetworkHandler'' is provided. This handler takes a request and converts it into the older form, calling the appropriate Adapter and Serializer methods. If no serializer exists for the type (including no application serializer), this handler calls ''next''. In this manner an app can incrementally migrate request-handling to this new paradigm on a per-type basis as desired. + In order to provide migration support for Adapters and Serializers, a ''LegacyNetworkHandler'' is provided. This handler takes a request and converts it into the older form, calling the appropriate Adapter and Serializer methods. If no adapter exists for the type (including no application adapter), this handler calls ''next''. In this manner an app can incrementally migrate request-handling to this new paradigm on a per-type basis as desired.
The package ''ember-data'' automatically configures this handler. If not using the ''ember-data'' package, this configuration needs to be done explicitly.
From 0fcead7592649a8c037410a7767ae2b76dd4321c Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Sun, 26 May 2024 20:58:26 +0200 Subject: [PATCH 25/25] LifetimesService => CachePolicy --- snippets/adapters/cache-lifetime/new.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snippets/adapters/cache-lifetime/new.js b/snippets/adapters/cache-lifetime/new.js index c66a113..a73ecd1 100644 --- a/snippets/adapters/cache-lifetime/new.js +++ b/snippets/adapters/cache-lifetime/new.js @@ -1,11 +1,11 @@ -import { LifetimesService } from '@ember-data/request-utils'; +import { CachePolicy } from '@ember-data/request-utils'; import BaseStore from 'ember-data/store'; export default class Store extends BaseStore { constructor(args) { super(args); // This is default configuration that would be set automatically be Ember Data - this.lifetimes = new LifetimesService(this, { + this.lifetimes = new CachePolicy(this, { apiCacheSoftExpires: 30_000, apiCacheHardExpires: 60_000 });