diff --git a/addon/-private/system/store.js b/addon/-private/system/store.js index 6b1c068c69d..cefef181064 100644 --- a/addon/-private/system/store.js +++ b/addon/-private/system/store.js @@ -417,14 +417,12 @@ Store = Service.extend({ /** This method returns a record for a given type and id combination. - The `findRecord` method will always return a **promise** that will be - resolved with the record. If the record was already in the store, - the promise will be resolved immediately. Otherwise, the store - will ask the adapter's `find` method to find the necessary data. - The `findRecord` method will always resolve its promise with the same object for a given type and `id`. + The `findRecord` method will always return a **promise** that will be + resolved with the record. + Example ```app/routes/post.js @@ -437,20 +435,89 @@ Store = Service.extend({ }); ``` - If you would like to force the record to reload, instead of - loading it from the cache when present you can set `reload: true` - in the options object for `findRecord`. + If the record is not yet available, the store will ask the adapter's `find` + method to find the necessary data. If the record is already present in the + store, it depends on the reload behavior _when_ the returned promise + resolves. - ```app/routes/post/edit.js - import Ember from 'ember'; + The reload behavior is configured either via the passed `options` hash or + the result of the adapter's `shouldReloadRecord`. - export default Ember.Route.extend({ - model: function(params) { - return this.store.findRecord('post', params.post_id, { reload: true }); + If `{ reload: true }` is passed or `adapter.shouldReloadRecord` evaluates + to `true`, then the returned promise resolves once the adapter returns + data, regardless if the requested record is already in the store: + + ```js + store.push({ + data: { + id: 1, + type: 'post', + revision: 1 + } + }); + + // adapter#findRecord resolves with + // [ + // { + // id: 1, + // type: 'post', + // revision: 2 + // } + // ] + store.findRecord('post', 1, { reload: true }).then(function(post) { + post.get("revision"); // 2 + }); + ``` + + If no reload is indicated via the abovementioned ways, then the promise + immediately resolves with the cached version in the store. + + Optionally, if `adapter.shouldBackgroundReloadRecord` evaluates to `true`, + then a background reload is started, which updates the records' data, once + it is available: + + ```js + // app/adapters/post.js + import ApplicationAdapter from "./application"; + + export default ApplicationAdapter.extend({ + shouldReloadRecord(store, snapshot) { + return false; + }, + + shouldBackgroundReloadRecord(store, snapshot) { + return true; + } + }); + + // ... + + store.push({ + data: { + id: 1, + type: 'post', + revision: 1 } }); + + var blogPost = store.findRecord('post', 1).then(function(post) { + post.get('revision'); // 1 + }); + + // later, once adapter#findRecord resolved with + // [ + // { + // id: 1, + // type: 'post', + // revision: 2 + // } + // ] + + blogPost.get('revision'); // 2 ``` + See [peekRecord](#method_peekRecord) to get the cached version of a record. + @method findRecord @param {String} modelName @param {(String|Integer)} id @@ -965,11 +1032,10 @@ Store = Service.extend({ }, /** - `findAll` ask the adapter's `findAll` method to find the records - for the given type, and return a promise that will be resolved - once the server returns the values. The promise will resolve into - all records of this type present in the store, even if the server - only returns a subset of them. + `findAll` ask the adapter's `findAll` method to find the records for the + given type, and returns a promise which will resolve with all records of + this type present in the store, even if the adapter only returns a subset + of them. ```app/routes/authors.js import Ember from 'ember'; @@ -981,6 +1047,85 @@ Store = Service.extend({ }); ``` + _When_ the returned promise resolves depends on the reload behavior, + configured via the passed `options` hash and the result of the adapter's + `shouldReloadAll` method. + + If `{ reload: true }` is passed or `adapter.shouldReloadAll` evaluates to + `true`, then the returned promise resolves once the adapter returns data, + regardless if there are already records in the store: + + ```js + store.push({ + data: { + id: 'first', + type: 'author' + } + }); + + // adapter#findAll resolves with + // [ + // { + // id: 'second', + // type: 'author' + // } + // ] + store.findAll('author', { reload: true }).then(function(authors) { + authors.getEach("id"); // ['first', 'second'] + }); + ``` + + If no reload is indicated via the abovementioned ways, then the promise + immediately resolves with all the records currently loaded in the store. + Optionally, if `adapter.shouldBackgroundReloadAll` evaluates to `true`, + then a background reload is started. Once this resolves, the array with + which the promise resolves, is updated automatically so it contains all the + records in the store: + + ```js + // app/adapters/application.js + export default DS.Adapter.extend({ + shouldReloadAll(store, snapshotsArray) { + return false; + }, + + shouldBackgroundReloadAll(store, snapshotsArray) { + return true; + } + }); + + // ... + + store.push({ + data: { + id: 'first', + type: 'author' + } + }); + + var allAuthors; + store.findAll('author').then(function(authors) { + authors.getEach('id'); // ['first'] + + allAuthors = authors; + }); + + // later, once adapter#findAll resolved with + // [ + // { + // id: 'second', + // type: 'author' + // } + // ] + + allAuthors.getEach('id'); // ['first', 'second'] + ``` + + See [peekAll](#method_peekAll) to get an array of current records in the + store, without waiting until a reload is finished. + + See [query](#method_query) to only get a subset of records from the server. + @method findAll @param {String} modelName @param {Object} options diff --git a/addon/adapter.js b/addon/adapter.js index e555e8e830e..ddc0ff27abf 100644 --- a/addon/adapter.js +++ b/addon/adapter.js @@ -465,10 +465,34 @@ export default Ember.Object.extend({ reload a record from the adapter when a record is requested by `store.findRecord`. - If this method returns true, the store will re-fetch a record from - the adapter. If this method returns false, the store will resolve + If this method returns `true`, the store will re-fetch a record from + the adapter. If this method returns `false`, the store will resolve immediately using the cached record. + For example, if you are building an events ticketing system, in which users + can only reserve tickets for 20 minutes at a time, and want to ensure that + in each route you have data that is no more than 20 minutes old you could + write: + + ```javascript + shouldReloadRecord: function(store, ticketSnapshot) { + var timeDiff = moment().diff(ticketSnapshot.attr('lastAccessedAt')).minutes(); + if (timeDiff > 20) { + return true; + } else { + return false; + } + } + ``` + + This method would ensure that whenever you do `store.findRecord('ticket', + id)` you will always get a ticket that is no more than 20 minutes old. In + case the cached version is more than 20 minutes old, `findRecord` will not + resolve until you fetched the latest version. + + By default this hook returns `false`, as most UIs should not block user + interactions while waiting on data update. + @method shouldReloadRecord @param {DS.Store} store @param {DS.Snapshot} snapshot @@ -483,9 +507,38 @@ export default Ember.Object.extend({ reload all records from the adapter when records are requested by `store.findAll`. - If this method returns true, the store will re-fetch all records from - the adapter. If this method returns false, the store will resolve - immediately using the cached record. + If this method returns `true`, the store will re-fetch all records from + the adapter. If this method returns `false`, the store will resolve + immediately using the cached records. + + For example, if you are building an events ticketing system, in which users + can only reserve tickets for 20 minutes at a time, and want to ensure that + in each route you have data that is no more than 20 minutes old you could + write: + + ```javascript + shouldReloadAll: function(store, snapshotArray) { + var snapshots = snapshotArray.snapshots(); + + return snapshots.any(function(ticketSnapshot) { + var timeDiff = moment().diff(ticketSnapshot.attr('lastAccessedAt')).minutes(); + if (timeDiff > 20) { + return true; + } else { + return false; + } + }); + } + ``` + + This method would ensure that whenever you do `store.findAll('ticket')` you + will always get a list of tickets that are no more than 20 minutes old. In + case a cached version is more than 20 minutes old, `findAll` will not + resolve until you fetched the latest versions. + + By default this methods returns `true` if the passed `snapshotRecordArray` + is empty (meaning that there are no records locally available yet), + otherwise it returns `false`. @method shouldReloadAll @param {DS.Store} store @@ -504,9 +557,27 @@ export default Ember.Object.extend({ This method is *only* checked by the store when the store is returning a cached record. - If this method returns true the store will re-fetch a record from + If this method returns `true` the store will re-fetch a record from the adapter. + For example, if you do not want to fetch complex data over a mobile + connection, or if the network is down, you can implement + `shouldBackgroundReloadRecord` as follows: + + ```javascript + shouldBackgroundReloadRecord: function(store, snapshot) { + var connection = window.navigator.connection; + if (connection === 'cellular' || connection === 'none') { + return false; + } else { + return true; + } + } + ``` + + By default this hook returns `true` so the data for the record is updated + in the background. + @method shouldBackgroundReloadRecord @param {DS.Store} store @param {DS.Snapshot} snapshot @@ -524,9 +595,27 @@ export default Ember.Object.extend({ This method is *only* checked by the store when the store is returning a cached record array. - If this method returns true the store will re-fetch all records + If this method returns `true` the store will re-fetch all records from the adapter. + For example, if you do not want to fetch complex data over a mobile + connection, or if the network is down, you can implement + `shouldBackgroundReloadAll` as follows: + + ```javascript + shouldBackgroundReloadAll: function(store, snapshotArray) { + var connection = window.navigator.connection; + if (connection === 'cellular' || connection === 'none') { + return false; + } else { + return true; + } + } + ``` + + By default this method returns `true`, indicating that a background reload + should always be triggered. + @method shouldBackgroundReloadAll @param {DS.Store} store @param {DS.SnapshotRecordArray} snapshotRecordArray