From 8e12a580b1a2119f34757a765e68920913d4e9a7 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 2 Oct 2023 11:39:28 +0200 Subject: [PATCH 1/6] #3 Write a section about migrating from Adapters --- app/routes/application.js | 169 ++---------------- package-lock.json | 4 +- 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 +- 15 files changed, 183 insertions(+), 161 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/package-lock.json b/package-lock.json index 1803fd1..550c3ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ember-data-request-service-cheat-sheet", - "version": "1.0.0", + "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ember-data-request-service-cheat-sheet", - "version": "1.0.0", + "version": "0.0.1", "license": "MIT", "devDependencies": { "@babel/eslint-parser": "^7.18.2", 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 debeb87d068449f2db4dc2a59467eb3dde8d0304 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Wed, 4 Oct 2023 10:57:17 +0200 Subject: [PATCH 2/6] 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 778b758ab89a40f4eb1f96f5f20bf2b66b2da061 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Wed, 4 Oct 2023 10:57:41 +0200 Subject: [PATCH 3/6] 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 0c417599c3933a5ed9d8f64e6e7d1ad7c497d477 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Wed, 4 Oct 2023 10:57:46 +0200 Subject: [PATCH 4/6] 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 1da2ce51f1b214f5e62d7e3249ecfd796075f1cb Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Wed, 4 Oct 2023 10:57:54 +0200 Subject: [PATCH 5/6] 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 2ed06b648cfcab29c3f25b765c3330cfed4e4b20 Mon Sep 17 00:00:00 2001 From: Kirill Shaplyko Date: Mon, 16 Oct 2023 13:37:29 +0200 Subject: [PATCH 6/6] 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'; + } + } } }