From b36292270d19d1b0d57ef1de3142e40e240aeb38 Mon Sep 17 00:00:00 2001 From: James Murphy Date: Sat, 14 Feb 2015 22:42:42 +1100 Subject: [PATCH] Adapters and Serializers are Store Managed Adapters and Serializers are now store managed - with each store instance holding a unique instance of each serializer and adapter. They are no longer singletons. This allows multiple stores to be used with ember-data. --- packages/ember-data/lib/initializers/store.js | 3 + .../lib/initializers/store_injections.js | 7 +- packages/ember-data/lib/system/store.js | 91 +++++++++-- .../tests/integration/multiple_stores_test.js | 148 ++++++++++++++++++ .../relationships/belongs_to_test.js | 8 +- .../embedded_records_mixin_test.js | 6 +- .../tests/integration/setup-container-test.js | 20 ++- tests/ember_configuration.js | 3 + 8 files changed, 260 insertions(+), 26 deletions(-) create mode 100644 packages/ember-data/tests/integration/multiple_stores_test.js diff --git a/packages/ember-data/lib/initializers/store.js b/packages/ember-data/lib/initializers/store.js index 4d20cacff61..641db02edfd 100644 --- a/packages/ember-data/lib/initializers/store.js +++ b/packages/ember-data/lib/initializers/store.js @@ -15,6 +15,9 @@ export default function initializeStore(container, application) { Ember.deprecate('Specifying a custom Store for Ember Data on your global namespace as `App.Store` ' + 'has been deprecated. Please use `App.ApplicationStore` instead.', !(application && application.Store)); + container.optionsForType('serializer', { singleton: false }); + container.optionsForType('adapter', { singleton: false }); + container.register('store:main', container.lookupFactory('store:application') || (application && application.Store) || Store); // allow older names to be looked up diff --git a/packages/ember-data/lib/initializers/store_injections.js b/packages/ember-data/lib/initializers/store_injections.js index 266c7424fd4..a3b681e31d9 100644 --- a/packages/ember-data/lib/initializers/store_injections.js +++ b/packages/ember-data/lib/initializers/store_injections.js @@ -5,9 +5,8 @@ @method initializeStoreInjections @param {Ember.Container} container */ -export default function initializeStoreInjections(container) { - container.injection('controller', 'store', 'store:main'); - container.injection('route', 'store', 'store:main'); - container.injection('serializer', 'store', 'store:main'); +export default function initializeStoreInjections(container){ + container.injection('controller', 'store', 'store:main'); + container.injection('route', 'store', 'store:main'); container.injection('data-adapter', 'store', 'store:main'); } diff --git a/packages/ember-data/lib/system/store.js b/packages/ember-data/lib/system/store.js index ed1ee142b85..426a8b94694 100644 --- a/packages/ember-data/lib/system/store.js +++ b/packages/ember-data/lib/system/store.js @@ -185,6 +185,7 @@ Store = Ember.Object.extend({ store: this }); this._pendingSave = []; + this._containerCache = Ember.create(null); //Used to keep track of all the find requests that need to be coalesced this._pendingFetch = Map.create(); }, @@ -251,7 +252,8 @@ Store = Ember.Object.extend({ if (DS.Adapter.detect(adapter)) { adapter = adapter.create({ - container: this.container + container: this.container, + store: this }); } @@ -1706,28 +1708,34 @@ Store = Ember.Object.extend({ // ...................... /** - Returns the adapter for a given type. - + Returns an instance of the adapter for a given type. For + example, `adapterFor('person')` will return an instance of + `App.PersonAdapter`. + + If no `App.PersonAdapter` is found, this method will look + for an `App.ApplicationAdapter` (the default adapter for + your entire application). + + If no `App.ApplicationAdapter` is found, it will return + the value of the `defaultAdapter`. + @method adapterFor @private - @param {subclass of DS.Model} type + @param {String or subclass of DS.Model} type @return DS.Adapter */ adapterFor: function(type) { - var adapter; - var container = this.container; - - if (container) { - adapter = container.lookup('adapter:' + type.typeKey) || container.lookup('adapter:application'); - } + type = this.modelFor(type); + var adapter = this.lookupAdapter(type.typeKey) || this.lookupAdapter('application'); + return adapter || get(this, 'defaultAdapter'); }, // .............................. // . RECORD CHANGE NOTIFICATION . // .............................. - + /** Returns an instance of the serializer for a given type. For example, `serializerFor('person')` will return an instance of @@ -1736,22 +1744,73 @@ Store = Ember.Object.extend({ If no `App.PersonSerializer` is found, this method will look for an `App.ApplicationSerializer` (the default serializer for your entire application). - - If no `App.ApplicationSerializer` is found, it will fall back + + if no `App.ApplicationSerializer` is found, it will attempt + to get the `defaultSerializer` from the `PersonAdapter` + (`adapterFor('person')`). + + If a serializer cannot be found on the adapter, it will fall back to an instance of `DS.JSONSerializer`. @method serializerFor @private - @param {String} type the record to serialize + @param {String or subclass of DS.Model} type the record to serialize @return {DS.Serializer} */ serializerFor: function(type) { type = this.modelFor(type); - var adapter = this.adapterFor(type); - return serializerFor(this.container, type.typeKey, adapter && adapter.defaultSerializer); + var serializer = this.lookupSerializer(type.typeKey) || this.lookupSerializer('application'); + + if (!serializer) { + var adapter = this.adapterFor(type); + serializer = this.lookupSerializer(adapter.defaultSerializer); + } + + if (!serializer) { + serializer = this.lookupSerializer('-default'); + } + + return serializer; }, + /** + Retrieve a particular instance from the + container cache. If not found, creates it and + placing it in the cache. + + Enabled a store to manage local instances of + adapters and serializers. + + @method retrieveManagedInstance + @private + @param {String} type the object type + @param {String} type the object name + @return {Ember.Object} + */ + retrieveManagedInstance: function(type, name) { + var key = type+":"+name; + + if (!this._containerCache[key]) { + var instance = this.container.lookup(key); + + if (instance) { + set(instance, 'store', this); + this._containerCache[key] = instance; + } + } + + return this._containerCache[key]; + }, + + lookupAdapter: function(name) { + return this.retrieveManagedInstance('adapter', name); + }, + + lookupSerializer: function(name) { + return this.retrieveManagedInstance('serializer', name); + }, + willDestroy: function() { var typeMaps = this.typeMaps; var keys = Ember.keys(typeMaps); diff --git a/packages/ember-data/tests/integration/multiple_stores_test.js b/packages/ember-data/tests/integration/multiple_stores_test.js new file mode 100644 index 00000000000..2a982b2ce27 --- /dev/null +++ b/packages/ember-data/tests/integration/multiple_stores_test.js @@ -0,0 +1,148 @@ +var get = Ember.get; +var HomePlanet, league, SuperVillain, EvilMinion, YellowMinion, DoomsdayDevice, MediocreVillain, env; +var run = Ember.run; + +module("integration/multiple_stores - Multiple Stores Tests", { + setup: function() { + SuperVillain = DS.Model.extend({ + firstName: DS.attr('string'), + lastName: DS.attr('string'), + homePlanet: DS.belongsTo("homePlanet", {inverse: 'villains'}), + evilMinions: DS.hasMany("evilMinion") + }); + HomePlanet = DS.Model.extend({ + name: DS.attr('string'), + villains: DS.hasMany('superVillain', {inverse: 'homePlanet'}) + }); + EvilMinion = DS.Model.extend({ + superVillain: DS.belongsTo('superVillain'), + name: DS.attr('string') + }); + + env = setupStore({ + superVillain: SuperVillain, + homePlanet: HomePlanet, + evilMinion: EvilMinion + }); + + env.store.modelFor('superVillain'); + env.store.modelFor('homePlanet'); + env.store.modelFor('evilMinion'); + + env.container.register('serializer:application', DS.ActiveModelSerializer); + env.container.register('serializer:-active-model', DS.ActiveModelSerializer); + env.container.register('adapter:-active-model', DS.ActiveModelAdapter); + + + env.container.register('store:store-a', DS.Store); + env.container.register('store:store-b', DS.Store); + + env.store_a = env.container.lookup('store:store-a'); + env.store_b = env.container.lookup('store:store-b'); + }, + + teardown: function() { + run(env.store, 'destroy'); + } +}); + +test("should be able to push into multiple stores", function() { + env.container.register('adapter:homePlanet', DS.ActiveModelAdapter); + env.container.register('serializer:homePlanet', DS.ActiveModelSerializer); + + var home_planet_main = { id: '1', name: 'Earth' }; + var home_planet_a = { id: '1', name: 'Mars' }; + var home_planet_b = { id: '1', name: 'Saturn' }; + + run(env.store, 'push', 'homePlanet', home_planet_main); + run(env.store_a, 'push', 'homePlanet', home_planet_a); + run(env.store_b, 'push', 'homePlanet', home_planet_b); + + run(env.store, 'find', 'homePlanet', 1).then(async(function(homePlanet) { + equal(homePlanet.get('name'), "Earth"); + })); + + run(env.store_a, 'find', 'homePlanet', 1).then(async(function(homePlanet) { + equal(homePlanet.get('name'), "Mars"); + })); + + run(env.store_b, 'find', 'homePlanet', 1).then(async(function(homePlanet) { + equal(homePlanet.get('name'), "Saturn"); + })); + +}); + +test("embedded records should be created in multiple stores", function() { + + env.container.register('store:primary', DS.Store); + env.container.register('store:secondary', DS.Store); + + env.primaryStore = env.container.lookup('store:primary'); + env.secondaryStore = env.container.lookup('store:secondary'); + + env.container.register('adapter:superVillain', DS.ActiveModelAdapter); + env.container.register('adapter:homePlanet', DS.ActiveModelAdapter); + + env.container.register('serializer:homePlanet', DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, { + attrs: { + villains: {embedded: 'always'} + } + })); + + var serializer = env.store.serializerFor("homePlanet"); + var serializer_primary = env.primaryStore.serializerFor("homePlanet"); + var serializer_secondary = env.secondaryStore.serializerFor("homePlanet"); + + var json_hash = { + home_planet: { + id: "1", + name: "Earth", + villains: [{ + id: "1", + first_name: "Tom", + last_name: "Dale" + }] + } + }; + var json_hash_primary = { + home_planet: { + id: "1", + name: "Mars", + villains: [{ + id: "1", + first_name: "James", + last_name: "Murphy" + }] + } + }; + var json_hash_secondary = { + home_planet: { + id: "1", + name: "Saturn", + villains: [{ + id: "1", + first_name: "Jade", + last_name: "John" + }] + } + }; + var json, json_primary, json_secondary; + + // debugger; + + run(function(){ + json = serializer.extractSingle(env.store, HomePlanet, json_hash); + equal(env.store.hasRecordForId("superVillain","1"), true, "superVillain should exist in store:main"); + }); + + run(function(){ + json_primary = serializer_primary.extractSingle(env.primaryStore, HomePlanet, json_hash_primary); + equal(env.primaryStore.hasRecordForId("superVillain","1"), true, "superVillain should exist in store:primary"); + }); + + run(function(){ + json_secondary = serializer_secondary.extractSingle(env.secondaryStore, HomePlanet, json_hash_secondary); + equal(env.secondaryStore.hasRecordForId("superVillain","1"), true, "superVillain should exist in store:secondary"); + }); + +}); \ No newline at end of file diff --git a/packages/ember-data/tests/integration/relationships/belongs_to_test.js b/packages/ember-data/tests/integration/relationships/belongs_to_test.js index ff2319b0358..c14bac6fc9a 100644 --- a/packages/ember-data/tests/integration/relationships/belongs_to_test.js +++ b/packages/ember-data/tests/integration/relationships/belongs_to_test.js @@ -63,6 +63,10 @@ module("integration/relationship/belongs_to Belongs-To Relationships", { author: Author }); + + env.registry.optionsForType('serializer', { singleton: false }); + env.registry.optionsForType('adapter', { singleton: false }); + env.registry.register('serializer:user', DS.JSONSerializer.extend({ attrs: { favouriteMessage: { embedded: 'always' } @@ -176,7 +180,9 @@ test("The store can load a polymorphic belongsTo association", function() { }); test("The store can serialize a polymorphic belongsTo association", function() { - env.serializer.serializePolymorphicType = function(record, json, relationship) { + var serializerInstance = store.serializerFor('comment'); + + serializerInstance.serializePolymorphicType = function(record, json, relationship) { ok(true, "The serializer's serializePolymorphicType method should be called"); json["message_type"] = "post"; }; diff --git a/packages/ember-data/tests/integration/serializers/embedded_records_mixin_test.js b/packages/ember-data/tests/integration/serializers/embedded_records_mixin_test.js index 5a4ff4595b1..5c9938af27b 100644 --- a/packages/ember-data/tests/integration/serializers/embedded_records_mixin_test.js +++ b/packages/ember-data/tests/integration/serializers/embedded_records_mixin_test.js @@ -61,9 +61,11 @@ module("integration/embedded_records_mixin - EmbeddedRecordsMixin", { env.store.modelFor('lightSaber'); env.store.modelFor('evilMinion'); env.store.modelFor('comment'); + env.container.optionsForType('serializer', { singleton: false }); + env.container.optionsForType('adapter', { singleton: false }); env.registry.register('serializer:application', DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin)); - env.registry.register('serializer:-active-model', DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin)); - env.registry.register('adapter:-active-model', DS.ActiveModelAdapter); + env.registry.register('serializer:-active-model', DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin)); + env.registry.register('adapter:-active-model', DS.ActiveModelAdapter); env.amsSerializer = env.container.lookup("serializer:-active-model"); env.amsAdapter = env.container.lookup("adapter:-active-model"); }, diff --git a/packages/ember-data/tests/integration/setup-container-test.js b/packages/ember-data/tests/integration/setup-container-test.js index 21447b31044..58956be52e7 100644 --- a/packages/ember-data/tests/integration/setup-container-test.js +++ b/packages/ember-data/tests/integration/setup-container-test.js @@ -39,7 +39,7 @@ test("the deprecated serializer:_default is resolved as serializer:default", fun deprecated = container.lookup('serializer:_default'); }); - ok(deprecated === valid, "they should resolve to the same thing"); + ok(deprecated.constructor === valid.constructor, "they should resolve to the same thing"); }); test("the deprecated serializer:_rest is resolved as serializer:rest", function() { @@ -49,7 +49,7 @@ test("the deprecated serializer:_rest is resolved as serializer:rest", function( deprecated = container.lookup('serializer:_rest'); }); - ok(deprecated === valid, "they should resolve to the same thing"); + ok(deprecated.constructor === valid.constructor, "they should resolve to the same thing"); }); test("the deprecated adapter:_rest is resolved as adapter:rest", function() { @@ -59,7 +59,7 @@ test("the deprecated adapter:_rest is resolved as adapter:rest", function() { deprecated = container.lookup('adapter:_rest'); }); - ok(deprecated === valid, "they should resolve to the same thing"); + ok(deprecated.constructor === valid.constructor, "they should resolve to the same thing"); }); test("a deprecation is made when looking up adapter:_rest", function() { @@ -67,3 +67,17 @@ test("a deprecation is made when looking up adapter:_rest", function() { container.lookup('serializer:_default'); }, "You tried to look up 'serializer:_default', but this has been deprecated in favor of 'serializer:-default'."); }); + +test("serializers are not returned as singletons - each lookup should return a different instance", function(){ + var serializer1, serializer2; + serializer1 = container.lookup('serializer:-rest'); + serializer2 = container.lookup('serializer:-rest'); + notEqual(serializer1,serializer2); +}); + +test("adapters are not returned as singletons - each lookup should return a different instance", function(){ + var adapter1, adapter2; + adapter1 = container.lookup('adapter:-rest'); + adapter2 = container.lookup('adapter:-rest'); + notEqual(adapter1,adapter2); +}); diff --git a/tests/ember_configuration.js b/tests/ember_configuration.js index 2235b8c2486..d7f05d6c1d5 100644 --- a/tests/ember_configuration.js +++ b/tests/ember_configuration.js @@ -79,6 +79,9 @@ adapter: adapter })); + registry.optionsForType('serializer', { singleton: false }); + registry.optionsForType('adapter', { singleton: false }); + registry.register('serializer:-default', DS.JSONSerializer); registry.register('serializer:-rest', DS.RESTSerializer); registry.register('adapter:-rest', DS.RESTAdapter);