PageRenderTime 261ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/frameworks/datastore/data_sources/fixtures.js

https://github.com/westoque/sproutcore
JavaScript | 403 lines | 181 code | 64 blank | 158 comment | 39 complexity | 293e896b8c7dd483b9375beda2a2da6a MD5 | raw file
  1. // ==========================================================================
  2. // Project: SproutCore - JavaScript Application Framework
  3. // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4. // Portions ©2008-2011 Apple Inc. All rights reserved.
  5. // License: Licensed under MIT license (see license.js)
  6. // ==========================================================================
  7. sc_require('data_sources/data_source');
  8. sc_require('models/record');
  9. /** @class
  10. TODO: Describe Class
  11. @extends SC.DataSource
  12. @since SproutCore 1.0
  13. */
  14. SC.FixturesDataSource = SC.DataSource.extend(
  15. /** @scope SC.FixturesDataSource.prototype */ {
  16. /**
  17. If YES then the data source will asynchronously respond to data requests
  18. from the server. If you plan to replace the fixture data source with a
  19. data source that talks to a real remote server (using Ajax for example),
  20. you should leave this property set to YES so that Fixtures source will
  21. more accurately simulate your remote data source.
  22. If you plan to replace this data source with something that works with
  23. local storage, for example, then you should set this property to NO to
  24. accurately simulate the behavior of your actual data source.
  25. @property {Boolean}
  26. */
  27. simulateRemoteResponse: NO,
  28. /**
  29. If you set simulateRemoteResponse to YES, then the fixtures source will
  30. assume a response latency from your server equal to the msec specified
  31. here. You should tune this to simulate latency based on the expected
  32. performance of your server network. Here are some good guidelines:
  33. - 500: Simulates a basic server written in PHP, Ruby, or Python (not twisted) without a CDN in front for caching.
  34. - 250: (Default) simulates the average latency needed to go back to your origin server from anywhere in the world. assumes your servers itself will respond to requests < 50 msec
  35. - 100: simulates the latency to a "nearby" server (i.e. same part of the world). Suitable for simulating locally hosted servers or servers with multiple data centers around the world.
  36. - 50: simulates the latency to an edge cache node when using a CDN. Life is really good if you can afford this kind of setup.
  37. @property {Number}
  38. */
  39. latency: 50,
  40. // ..........................................................
  41. // CANCELLING
  42. //
  43. /** @private */
  44. cancel: function(store, storeKeys) {
  45. return NO;
  46. },
  47. // ..........................................................
  48. // FETCHING
  49. //
  50. /** @private */
  51. fetch: function(store, query) {
  52. // can only handle local queries out of the box
  53. if (query.get('location') !== SC.Query.LOCAL) {
  54. throw SC.$error('SC.Fixture data source can only fetch local queries');
  55. }
  56. if (!query.get('recordType') && !query.get('recordTypes')) {
  57. throw SC.$error('SC.Fixture data source can only fetch queries with one or more record types');
  58. }
  59. if (this.get('simulateRemoteResponse')) {
  60. this.invokeLater(this._fetch, this.get('latency'), store, query);
  61. } else this._fetch(store, query);
  62. },
  63. /** @private
  64. Actually performs the fetch.
  65. */
  66. _fetch: function(store, query) {
  67. // NOTE: Assumes recordType or recordTypes is defined. checked in fetch()
  68. var recordType = query.get('recordType'),
  69. recordTypes = query.get('recordTypes') || [recordType];
  70. // load fixtures for each recordType
  71. recordTypes.forEach(function(recordType) {
  72. if (SC.typeOf(recordType) === SC.T_STRING) {
  73. recordType = SC.objectForPropertyPath(recordType);
  74. }
  75. if (recordType) this.loadFixturesFor(store, recordType);
  76. }, this);
  77. // notify that query has now loaded - puts it into a READY state
  78. store.dataSourceDidFetchQuery(query);
  79. },
  80. // ..........................................................
  81. // RETRIEVING
  82. //
  83. /** @private */
  84. retrieveRecords: function(store, storeKeys) {
  85. // first let's see if the fixture data source can handle any of the
  86. // storeKeys
  87. var latency = this.get('latency'),
  88. ret = this.hasFixturesFor(storeKeys) ;
  89. if (!ret) return ret ;
  90. if (this.get('simulateRemoteResponse')) {
  91. this.invokeLater(this._retrieveRecords, latency, store, storeKeys);
  92. } else this._retrieveRecords(store, storeKeys);
  93. return ret ;
  94. },
  95. _retrieveRecords: function(store, storeKeys) {
  96. storeKeys.forEach(function(storeKey) {
  97. var ret = [],
  98. recordType = SC.Store.recordTypeFor(storeKey),
  99. id = store.idFor(storeKey),
  100. hash = this.fixtureForStoreKey(store, storeKey);
  101. ret.push(storeKey);
  102. store.dataSourceDidComplete(storeKey, hash, id);
  103. }, this);
  104. },
  105. // ..........................................................
  106. // UPDATE
  107. //
  108. /** @private */
  109. updateRecords: function(store, storeKeys, params) {
  110. // first let's see if the fixture data source can handle any of the
  111. // storeKeys
  112. var latency = this.get('latency'),
  113. ret = this.hasFixturesFor(storeKeys) ;
  114. if (!ret) return ret ;
  115. if (this.get('simulateRemoteResponse')) {
  116. this.invokeLater(this._updateRecords, latency, store, storeKeys);
  117. } else this._updateRecords(store, storeKeys);
  118. return ret ;
  119. },
  120. _updateRecords: function(store, storeKeys) {
  121. storeKeys.forEach(function(storeKey) {
  122. var hash = store.readDataHash(storeKey);
  123. this.setFixtureForStoreKey(store, storeKey, hash);
  124. store.dataSourceDidComplete(storeKey);
  125. }, this);
  126. },
  127. // ..........................................................
  128. // CREATE RECORDS
  129. //
  130. /** @private */
  131. createRecords: function(store, storeKeys, params) {
  132. // first let's see if the fixture data source can handle any of the
  133. // storeKeys
  134. var latency = this.get('latency');
  135. if (this.get('simulateRemoteResponse')) {
  136. this.invokeLater(this._createRecords, latency, store, storeKeys);
  137. } else this._createRecords(store, storeKeys);
  138. return YES ;
  139. },
  140. _createRecords: function(store, storeKeys) {
  141. storeKeys.forEach(function(storeKey) {
  142. var id = store.idFor(storeKey),
  143. recordType = store.recordTypeFor(storeKey),
  144. dataHash = store.readDataHash(storeKey),
  145. fixtures = this.fixturesFor(recordType);
  146. if (!id) id = this.generateIdFor(recordType, dataHash, store, storeKey);
  147. this._invalidateCachesFor(recordType, storeKey, id);
  148. fixtures[id] = dataHash;
  149. store.dataSourceDidComplete(storeKey, null, id);
  150. }, this);
  151. },
  152. // ..........................................................
  153. // DESTROY RECORDS
  154. //
  155. /** @private */
  156. destroyRecords: function(store, storeKeys, params) {
  157. // first let's see if the fixture data source can handle any of the
  158. // storeKeys
  159. var latency = this.get('latency'),
  160. ret = this.hasFixturesFor(storeKeys) ;
  161. if (!ret) return ret ;
  162. if (this.get('simulateRemoteResponse')) {
  163. this.invokeLater(this._destroyRecords, latency, store, storeKeys);
  164. } else this._destroyRecords(store, storeKeys);
  165. return ret ;
  166. },
  167. _destroyRecords: function(store, storeKeys) {
  168. storeKeys.forEach(function(storeKey) {
  169. var id = store.idFor(storeKey),
  170. recordType = store.recordTypeFor(storeKey),
  171. fixtures = this.fixturesFor(recordType);
  172. this._invalidateCachesFor(recordType, storeKey, id);
  173. if (id) delete fixtures[id];
  174. store.dataSourceDidDestroy(storeKey);
  175. }, this);
  176. },
  177. // ..........................................................
  178. // INTERNAL METHODS/PRIMITIVES
  179. //
  180. /**
  181. Load fixtures for a given fetchKey into the store
  182. and push it to the ret array.
  183. @param {SC.Store} store the store to load into
  184. @param {SC.Record} recordType the record type to load
  185. @param {SC.Array} ret is passed, array to add loaded storeKeys to.
  186. @returns {SC.FixturesDataSource} receiver
  187. */
  188. loadFixturesFor: function(store, recordType, ret) {
  189. var hashes = [],
  190. dataHashes, i, storeKey ;
  191. dataHashes = this.fixturesFor(recordType);
  192. for(i in dataHashes){
  193. storeKey = recordType.storeKeyFor(i);
  194. if (store.peekStatus(storeKey) === SC.Record.EMPTY) {
  195. hashes.push(dataHashes[i]);
  196. }
  197. if (ret) ret.push(storeKey);
  198. }
  199. // only load records that were not already loaded to avoid infinite loops
  200. if (hashes && hashes.length>0) store.loadRecords(recordType, hashes);
  201. return this ;
  202. },
  203. /**
  204. Generates an id for the passed record type. You can override this if
  205. needed. The default generates a storekey and formats it as a string.
  206. @param {Class} recordType Subclass of SC.Record
  207. @param {Hash} dataHash the data hash for the record
  208. @param {SC.Store} store the store
  209. @param {Number} storeKey store key for the item
  210. @returns {String}
  211. */
  212. generateIdFor: function(recordType, dataHash, store, storeKey) {
  213. return "@id%@".fmt(SC.Store.generateStoreKey());
  214. },
  215. /**
  216. Based on the storeKey it returns the specified fixtures
  217. @param {SC.Store} store the store
  218. @param {Number} storeKey the storeKey
  219. @returns {Hash} data hash or null
  220. */
  221. fixtureForStoreKey: function(store, storeKey) {
  222. var id = store.idFor(storeKey),
  223. recordType = store.recordTypeFor(storeKey),
  224. fixtures = this.fixturesFor(recordType);
  225. return fixtures ? fixtures[id] : null;
  226. },
  227. /**
  228. Update the data hash fixture for the named store key.
  229. @param {SC.Store} store the store
  230. @param {Number} storeKey the storeKey
  231. @param {Hash} dataHash
  232. @returns {SC.FixturesDataSource} receiver
  233. */
  234. setFixtureForStoreKey: function(store, storeKey, dataHash) {
  235. var id = store.idFor(storeKey),
  236. recordType = store.recordTypeFor(storeKey),
  237. fixtures = this.fixturesFor(recordType);
  238. this._invalidateCachesFor(recordType, storeKey, id);
  239. fixtures[id] = dataHash;
  240. return this ;
  241. },
  242. /**
  243. Get the fixtures for the passed record type and prepare them if needed.
  244. Return cached value when complete.
  245. @param {SC.Record} recordType
  246. @returns {Hash} data hashes
  247. */
  248. fixturesFor: function(recordType) {
  249. // get basic fixtures hash.
  250. if (!this._fixtures) this._fixtures = {};
  251. var fixtures = this._fixtures[SC.guidFor(recordType)];
  252. if (fixtures) return fixtures ;
  253. // need to load fixtures.
  254. var dataHashes = recordType ? recordType.FIXTURES : null,
  255. len = dataHashes ? dataHashes.length : 0,
  256. primaryKey = recordType ? recordType.prototype.primaryKey : 'guid',
  257. idx, dataHash, id ;
  258. this._fixtures[SC.guidFor(recordType)] = fixtures = {} ;
  259. for(idx=0;idx<len;idx++) {
  260. dataHash = dataHashes[idx];
  261. id = dataHash[primaryKey];
  262. if (!id) id = this.generateIdFor(recordType, dataHash);
  263. fixtures[id] = dataHash;
  264. }
  265. return fixtures;
  266. },
  267. /**
  268. Returns YES if fixtures for a given recordType have already been loaded
  269. @param {SC.Record} recordType
  270. @returns {Boolean} storeKeys
  271. */
  272. fixturesLoadedFor: function(recordType) {
  273. if (!this._fixtures) return NO;
  274. var ret = [], fixtures = this._fixtures[SC.guidFor(recordType)];
  275. return fixtures ? YES: NO;
  276. },
  277. /**
  278. Resets the fixtures to their original values.
  279. @returns {SC.FixturesDataSource} receiver
  280. */
  281. reset: function(){
  282. this._fixtures = null;
  283. return this;
  284. },
  285. /**
  286. Returns YES or SC.MIXED_STATE if one or more of the storeKeys can be
  287. handled by the fixture data source.
  288. @param {Array} storeKeys the store keys
  289. @returns {Boolean} YES if all handled, MIXED_STATE if some handled
  290. */
  291. hasFixturesFor: function(storeKeys) {
  292. var ret = NO ;
  293. storeKeys.forEach(function(storeKey) {
  294. if (ret !== SC.MIXED_STATE) {
  295. var recordType = SC.Store.recordTypeFor(storeKey),
  296. fixtures = recordType ? recordType.FIXTURES : null ;
  297. if (fixtures && fixtures.length && fixtures.length>0) {
  298. if (ret === NO) ret = YES ;
  299. } else if (ret === YES) ret = SC.MIXED_STATE ;
  300. }
  301. }, this);
  302. return ret ;
  303. },
  304. /** @private
  305. Invalidates any internal caches based on the recordType and optional
  306. other parameters. Currently this only invalidates the storeKeyCache used
  307. for fetch, but it could invalidate others later as well.
  308. @param {SC.Record} recordType the type of record modified
  309. @param {Number} storeKey optional store key
  310. @param {String} id optional record id
  311. @returns {SC.FixturesDataSource} receiver
  312. */
  313. _invalidateCachesFor: function(recordType, storeKey, id) {
  314. var cache = this._storeKeyCache;
  315. if (cache) delete cache[SC.guidFor(recordType)];
  316. return this ;
  317. }
  318. });
  319. /**
  320. Default fixtures instance for use in applications.
  321. @property {SC.FixturesDataSource}
  322. */
  323. SC.Record.fixtures = SC.FixturesDataSource.create();