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 authored and bmac committed Mar 22, 2015
1 parent 5eef713 commit 31583db
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 37 deletions.
1 change: 0 additions & 1 deletion packages/ember-data/lib/initializers/store-injections.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@
export default function initializeStoreInjections(registry) {
registry.injection('controller', 'store', 'store:main');
registry.injection('route', 'store', 'store:main');
registry.injection('serializer', 'store', 'store:main');
registry.injection('data-adapter', 'store', 'store:main');
}
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(registry, 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));

registry.optionsForType('serializer', { singleton: false });
registry.optionsForType('adapter', { singleton: false });

registry.register('store:main', registry.lookupFactory('store:application') || (application && application.Store) || Store);

// allow older names to be looked up
Expand Down
92 changes: 78 additions & 14 deletions packages/ember-data/lib/system/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
} from "ember-data/system/store/common";

import {
serializerFor,
serializerForAdapter
} from "ember-data/system/store/serializers";

Expand Down Expand Up @@ -210,6 +209,7 @@ Store = Service.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 @@ -276,7 +276,8 @@ Store = Service.extend({

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

Expand Down Expand Up @@ -1785,20 +1786,26 @@ Store = Service.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;
type = this.modelFor(type);

if (container) {
adapter = container.lookup('adapter:' + type.typeKey) || container.lookup('adapter:application');
}
var adapter = this.lookupAdapter(type.typeKey) || this.lookupAdapter('application');

return adapter || get(this, 'defaultAdapter');
},
Expand All @@ -1820,19 +1827,70 @@ Store = Service.extend({
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(get(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() {
Expand All @@ -1849,6 +1907,12 @@ Store = Service.extend({
return typeMaps[entry]['type'];
}

for (var cacheKey in this._containerCache) {
this._containerCache[cacheKey].destroy();
delete this._containerCache[cacheKey];
}

delete this._containerCache;
},

/**
Expand Down Expand Up @@ -1929,7 +1993,7 @@ function defaultSerializer(container) {
function _commit(adapter, store, operation, record) {
var type = record.constructor;
var promise = adapter[operation](store, type, record);
var serializer = serializerForAdapter(adapter, type);
var serializer = serializerForAdapter(store, adapter, type);
var label = "DS: Extract and notify about " + operation + " completion of " + record;

Ember.assert("Your adapter's '" + operation + "' method must return a value, but it returned `undefined", promise !==undefined);
Expand Down
12 changes: 6 additions & 6 deletions packages/ember-data/lib/system/store/finders.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var Promise = Ember.RSVP.Promise;

export function _find(adapter, store, type, id, record) {
var promise = adapter.find(store, type, id, record);
var serializer = serializerForAdapter(adapter, type);
var serializer = serializerForAdapter(store, adapter, type);
var label = "DS: Handle Adapter#find of " + type + " with id: " + id;

promise = Promise.cast(promise, label);
Expand Down Expand Up @@ -42,7 +42,7 @@ export function _find(adapter, store, type, id, record) {

export function _findMany(adapter, store, type, ids, records) {
var promise = adapter.findMany(store, type, ids, records);
var serializer = serializerForAdapter(adapter, type);
var serializer = serializerForAdapter(store, adapter, type);
var label = "DS: Handle Adapter#findMany of " + type;

if (promise === undefined) {
Expand All @@ -65,7 +65,7 @@ export function _findMany(adapter, store, type, ids, records) {

export function _findHasMany(adapter, store, record, link, relationship) {
var promise = adapter.findHasMany(store, record, link, relationship);
var serializer = serializerForAdapter(adapter, relationship.type);
var serializer = serializerForAdapter(store, adapter, relationship.type);
var label = "DS: Handle Adapter#findHasMany of " + record + " : " + relationship.type;

promise = Promise.cast(promise, label);
Expand All @@ -86,7 +86,7 @@ export function _findHasMany(adapter, store, record, link, relationship) {

export function _findBelongsTo(adapter, store, record, link, relationship) {
var promise = adapter.findBelongsTo(store, record, link, relationship);
var serializer = serializerForAdapter(adapter, relationship.type);
var serializer = serializerForAdapter(store, adapter, relationship.type);
var label = "DS: Handle Adapter#findBelongsTo of " + record + " : " + relationship.type;

promise = Promise.cast(promise, label);
Expand All @@ -109,7 +109,7 @@ export function _findBelongsTo(adapter, store, record, link, relationship) {

export function _findAll(adapter, store, type, sinceToken) {
var promise = adapter.findAll(store, type, sinceToken);
var serializer = serializerForAdapter(adapter, type);
var serializer = serializerForAdapter(store, adapter, type);
var label = "DS: Handle Adapter#findAll of " + type;

promise = Promise.cast(promise, label);
Expand All @@ -131,7 +131,7 @@ export function _findAll(adapter, store, type, sinceToken) {

export function _findQuery(adapter, store, type, query, recordArray) {
var promise = adapter.findQuery(store, type, query, recordArray);
var serializer = serializerForAdapter(adapter, type);
var serializer = serializerForAdapter(store, adapter, type);
var label = "DS: Handle Adapter#findQuery of " + type;

promise = Promise.cast(promise, label);
Expand Down
15 changes: 3 additions & 12 deletions packages/ember-data/lib/system/store/serializers.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
export function serializerFor(container, type, defaultSerializer) {
return container.lookup('serializer:'+type) ||
container.lookup('serializer:application') ||
container.lookup('serializer:' + defaultSerializer) ||
container.lookup('serializer:-default');
}

export function serializerForAdapter(adapter, type) {
export function serializerForAdapter(store, adapter, type) {
var serializer = adapter.serializer;
var defaultSerializer = adapter.defaultSerializer;
var container = adapter.container;

if (container && serializer === undefined) {
serializer = serializerFor(container, type.typeKey, defaultSerializer);
if (serializer === undefined) {
serializer = store.serializerFor(type);
}

if (serializer === null || serializer === undefined) {
Expand Down
134 changes: 134 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,134 @@
var env;
var SuperVillain, HomePlanet, EvilMinion;
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.registry.register('serializer:application', DS.ActiveModelSerializer);
env.registry.register('serializer:-active-model', DS.ActiveModelSerializer);
env.registry.register('adapter:-active-model', DS.ActiveModelAdapter);

env.registry.register('store:store-a', DS.Store);
env.registry.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.registry.register('adapter:homePlanet', DS.ActiveModelAdapter);
env.registry.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.registry.register('adapter:superVillain', DS.ActiveModelAdapter);
env.registry.register('adapter:homePlanet', DS.ActiveModelAdapter);

env.registry.register('serializer:homePlanet', DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
villains: { embedded: 'always' }
}
}));

var serializer_main = env.store.serializerFor("homePlanet");
var serializer_a = env.store_a.serializerFor("homePlanet");
var serializer_b = env.store_b.serializerFor("homePlanet");

var json_hash_main = {
home_planet: {
id: "1",
name: "Earth",
villains: [{
id: "1",
first_name: "Tom",
last_name: "Dale"
}]
}
};
var json_hash_a = {
home_planet: {
id: "1",
name: "Mars",
villains: [{
id: "1",
first_name: "James",
last_name: "Murphy"
}]
}
};
var json_hash_b = {
home_planet: {
id: "1",
name: "Saturn",
villains: [{
id: "1",
first_name: "Jade",
last_name: "John"
}]
}
};
var json_main, json_a, json_b;

run(function() {
json_main = serializer_main.extractSingle(env.store, HomePlanet, json_hash_main);
equal(env.store.hasRecordForId("superVillain", "1"), true, "superVillain should exist in store:main");
});

run(function() {
json_a = serializer_a.extractSingle(env.store_a, HomePlanet, json_hash_a);
equal(env.store_a.hasRecordForId("superVillain", "1"), true, "superVillain should exist in store:store-a");
});

run(function() {
json_b = serializer_b.extractSingle(env.store_b, HomePlanet, json_hash_b);
equal(env.store_b.hasRecordForId("superVillain", "1"), true, "superVillain should exist in store:store-b");
});

});
Loading

0 comments on commit 31583db

Please sign in to comment.