Skip to content

Commit

Permalink
Adapters and Serializers are Store Managed
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jmurphyau committed Feb 14, 2015
1 parent 64ccb96 commit b362922
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 26 deletions.
3 changes: 3 additions & 0 deletions packages/ember-data/lib/initializers/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 3 additions & 4 deletions packages/ember-data/lib/initializers/store_injections.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
91 changes: 75 additions & 16 deletions packages/ember-data/lib/system/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
},
Expand Down Expand Up @@ -251,7 +252,8 @@ Store = Ember.Object.extend({

if (DS.Adapter.detect(adapter)) {
adapter = adapter.create({
container: this.container
container: this.container,
store: this
});
}

Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down
148 changes: 148 additions & 0 deletions packages/ember-data/tests/integration/multiple_stores_test.js
Original file line number Diff line number Diff line change
@@ -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");
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
Expand Down Expand Up @@ -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";
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
},
Expand Down
20 changes: 17 additions & 3 deletions packages/ember-data/tests/integration/setup-container-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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() {
Expand All @@ -59,11 +59,25 @@ 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() {
expectDeprecation(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);
});
Loading

0 comments on commit b362922

Please sign in to comment.