mirror of
https://github.com/discourse/discourse.git
synced 2025-01-30 06:28:03 +08:00
DEV: Make store
an actual service (#14996)
`store:main` was unofficially deprecated 4 years ago in fbd5f1e411 (diff-b19dd1d6a5c7938fda9ae317136bbbb82339946ab457c9b95af936a47276c3ddR22)
This commit is contained in:
parent
19627eca4b
commit
42fff92d9f
|
@ -1,383 +1,9 @@
|
|||
import EmberObject, { set } from "@ember/object";
|
||||
import { Promise } from "rsvp";
|
||||
import RestModel from "discourse/models/rest";
|
||||
import ResultSet from "discourse/models/result-set";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { getRegister } from "discourse-common/lib/get-owner";
|
||||
import { underscore } from "@ember/string";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
export { default, flushMap } from "discourse/services/store";
|
||||
|
||||
let _identityMap;
|
||||
|
||||
// You should only call this if you're a test scaffold
|
||||
function flushMap() {
|
||||
_identityMap = {};
|
||||
}
|
||||
|
||||
function storeMap(type, id, obj) {
|
||||
if (!id) {
|
||||
return;
|
||||
deprecated(
|
||||
`"discourse/models/store" import is deprecated, use "discourse/services/store" instead`,
|
||||
{
|
||||
since: "2.8.0.beta8",
|
||||
}
|
||||
|
||||
_identityMap[type] = _identityMap[type] || {};
|
||||
_identityMap[type][id] = obj;
|
||||
}
|
||||
|
||||
function fromMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType && byType.hasOwnProperty(id)) {
|
||||
return byType[id];
|
||||
}
|
||||
}
|
||||
|
||||
function removeMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType && byType.hasOwnProperty(id)) {
|
||||
delete byType[id];
|
||||
}
|
||||
}
|
||||
|
||||
function findAndRemoveMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType && byType.hasOwnProperty(id)) {
|
||||
const result = byType[id];
|
||||
delete byType[id];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
flushMap();
|
||||
|
||||
export default EmberObject.extend({
|
||||
_plurals: {
|
||||
category: "categories",
|
||||
"post-reply": "post-replies",
|
||||
"post-reply-history": "post_reply_histories",
|
||||
reviewable_history: "reviewable_histories",
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.register = this.register || getRegister(this);
|
||||
},
|
||||
|
||||
pluralize(thing) {
|
||||
return this._plurals[thing] || thing + "s";
|
||||
},
|
||||
|
||||
addPluralization(thing, plural) {
|
||||
this._plurals[thing] = plural;
|
||||
},
|
||||
|
||||
findAll(type, findArgs) {
|
||||
const adapter = this.adapterFor(type);
|
||||
|
||||
let store = this;
|
||||
return adapter.findAll(this, type, findArgs).then((result) => {
|
||||
let results = this._resultSet(type, result);
|
||||
if (adapter.afterFindAll) {
|
||||
results = adapter.afterFindAll(results, {
|
||||
lookup(subType, id) {
|
||||
return store._lookupSubType(subType, type, id, result);
|
||||
},
|
||||
});
|
||||
}
|
||||
return results;
|
||||
});
|
||||
},
|
||||
|
||||
// Mostly for legacy, things like TopicList without ResultSets
|
||||
findFiltered(type, findArgs) {
|
||||
return this.adapterFor(type)
|
||||
.find(this, type, findArgs)
|
||||
.then((result) => this._build(type, result));
|
||||
},
|
||||
|
||||
_hydrateFindResults(result, type, findArgs) {
|
||||
if (typeof findArgs === "object") {
|
||||
return this._resultSet(type, result, findArgs);
|
||||
} else {
|
||||
const apiName = this.adapterFor(type).apiNameFor(type);
|
||||
return this._hydrate(type, result[underscore(apiName)], result);
|
||||
}
|
||||
},
|
||||
|
||||
// See if the store can find stale data. We sometimes prefer to show stale data and
|
||||
// refresh it in the background.
|
||||
findStale(type, findArgs, opts) {
|
||||
const stale = this.adapterFor(type).findStale(this, type, findArgs, opts);
|
||||
return {
|
||||
hasResults: stale !== undefined,
|
||||
results: stale,
|
||||
refresh: () => this.find(type, findArgs, opts),
|
||||
};
|
||||
},
|
||||
|
||||
find(type, findArgs, opts) {
|
||||
let adapter = this.adapterFor(type);
|
||||
return adapter.find(this, type, findArgs, opts).then((result) => {
|
||||
let hydrated = this._hydrateFindResults(result, type, findArgs, opts);
|
||||
|
||||
if (result.extras) {
|
||||
hydrated.set("extras", result.extras);
|
||||
}
|
||||
|
||||
if (adapter.cache) {
|
||||
const stale = adapter.findStale(this, type, findArgs, opts);
|
||||
hydrated = this._updateStale(stale, hydrated, adapter.primaryKey);
|
||||
adapter.cacheFind(this, type, findArgs, opts, hydrated);
|
||||
}
|
||||
return hydrated;
|
||||
});
|
||||
},
|
||||
|
||||
_updateStale(stale, hydrated, primaryKey) {
|
||||
if (!stale) {
|
||||
return hydrated;
|
||||
}
|
||||
|
||||
hydrated.set(
|
||||
"content",
|
||||
hydrated.get("content").map((item) => {
|
||||
let staleItem = stale.content.findBy(primaryKey, item.get(primaryKey));
|
||||
if (staleItem) {
|
||||
staleItem.setProperties(item);
|
||||
} else {
|
||||
staleItem = item;
|
||||
}
|
||||
return staleItem;
|
||||
})
|
||||
);
|
||||
return hydrated;
|
||||
},
|
||||
|
||||
refreshResults(resultSet, type, url) {
|
||||
const adapter = this.adapterFor(type);
|
||||
return ajax(url).then((result) => {
|
||||
const typeName = underscore(this.pluralize(adapter.apiNameFor(type)));
|
||||
const content = result[typeName].map((obj) =>
|
||||
this._hydrate(type, obj, result)
|
||||
);
|
||||
resultSet.set("content", content);
|
||||
});
|
||||
},
|
||||
|
||||
appendResults(resultSet, type, url) {
|
||||
const adapter = this.adapterFor(type);
|
||||
return ajax(url).then((result) => {
|
||||
const typeName = underscore(this.pluralize(adapter.apiNameFor(type)));
|
||||
|
||||
let pageTarget = result.meta || result;
|
||||
let totalRows =
|
||||
pageTarget["total_rows_" + typeName] || resultSet.get("totalRows");
|
||||
let loadMoreUrl = pageTarget["load_more_" + typeName];
|
||||
let content = result[typeName].map((obj) =>
|
||||
this._hydrate(type, obj, result)
|
||||
);
|
||||
|
||||
resultSet.setProperties({ totalRows, loadMoreUrl });
|
||||
resultSet.get("content").pushObjects(content);
|
||||
|
||||
// If we've loaded them all, clear the load more URL
|
||||
if (resultSet.get("length") >= totalRows) {
|
||||
resultSet.set("loadMoreUrl", null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
update(type, id, attrs) {
|
||||
const adapter = this.adapterFor(type);
|
||||
return adapter.update(this, type, id, attrs, function (result) {
|
||||
if (result && result[type] && result[type][adapter.primaryKey]) {
|
||||
const oldRecord = findAndRemoveMap(type, id);
|
||||
storeMap(type, result[type][adapter.primaryKey], oldRecord);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
createRecord(type, attrs) {
|
||||
attrs = attrs || {};
|
||||
const adapter = this.adapterFor(type);
|
||||
return !!attrs[adapter.primaryKey]
|
||||
? this._hydrate(type, attrs)
|
||||
: this._build(type, attrs);
|
||||
},
|
||||
|
||||
destroyRecord(type, record) {
|
||||
const adapter = this.adapterFor(type);
|
||||
|
||||
// If the record is new, don't perform an Ajax call
|
||||
if (record.get("isNew")) {
|
||||
removeMap(type, record.get(adapter.primaryKey));
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
return adapter.destroyRecord(this, type, record).then(function (result) {
|
||||
removeMap(type, record.get(adapter.primaryKey));
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
_resultSet(type, result, findArgs) {
|
||||
const adapter = this.adapterFor(type);
|
||||
const typeName = underscore(this.pluralize(adapter.apiNameFor(type)));
|
||||
|
||||
if (!result[typeName]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`JSON response is missing \`${typeName}\` key`, result);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = result[typeName].map((obj) =>
|
||||
this._hydrate(type, obj, result)
|
||||
);
|
||||
|
||||
let pageTarget = result.meta || result;
|
||||
|
||||
const createArgs = {
|
||||
content,
|
||||
findArgs,
|
||||
totalRows: pageTarget["total_rows_" + typeName] || content.length,
|
||||
loadMoreUrl: pageTarget["load_more_" + typeName],
|
||||
refreshUrl: pageTarget["refresh_" + typeName],
|
||||
resultSetMeta: result.meta,
|
||||
store: this,
|
||||
__type: type,
|
||||
};
|
||||
|
||||
if (result.extras) {
|
||||
createArgs.extras = result.extras;
|
||||
}
|
||||
|
||||
return ResultSet.create(createArgs);
|
||||
},
|
||||
|
||||
_build(type, obj) {
|
||||
const adapter = this.adapterFor(type);
|
||||
obj.store = this;
|
||||
obj.__type = type;
|
||||
obj.__state = obj[adapter.primaryKey] ? "created" : "new";
|
||||
|
||||
// TODO: Have injections be automatic
|
||||
obj.topicTrackingState = this.register.lookup("topic-tracking-state:main");
|
||||
obj.keyValueStore = this.register.lookup("key-value-store:main");
|
||||
|
||||
const klass = this.register.lookupFactory("model:" + type) || RestModel;
|
||||
const model = klass.create(obj);
|
||||
|
||||
storeMap(type, obj[adapter.primaryKey], model);
|
||||
return model;
|
||||
},
|
||||
|
||||
adapterFor(type) {
|
||||
return (
|
||||
this.register.lookup("adapter:" + type) ||
|
||||
this.register.lookup("adapter:rest")
|
||||
);
|
||||
},
|
||||
|
||||
_lookupSubType(subType, type, id, root) {
|
||||
if (root.meta && root.meta.types) {
|
||||
subType = root.meta.types[subType] || subType;
|
||||
}
|
||||
|
||||
const subTypeAdapter = this.adapterFor(subType);
|
||||
const pluralType = this.pluralize(subType);
|
||||
const collection = root[this.pluralize(subType)];
|
||||
if (collection) {
|
||||
const hashedProp = "__hashed_" + pluralType;
|
||||
let hashedCollection = root[hashedProp];
|
||||
if (!hashedCollection) {
|
||||
hashedCollection = {};
|
||||
collection.forEach(function (it) {
|
||||
hashedCollection[it[subTypeAdapter.primaryKey]] = it;
|
||||
});
|
||||
root[hashedProp] = hashedCollection;
|
||||
}
|
||||
|
||||
const found = hashedCollection[id];
|
||||
if (found) {
|
||||
const hydrated = this._hydrate(subType, found, root);
|
||||
hashedCollection[id] = hydrated;
|
||||
return hydrated;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_hydrateEmbedded(type, obj, root) {
|
||||
const adapter = this.adapterFor(type);
|
||||
Object.keys(obj).forEach((k) => {
|
||||
if (k === adapter.primaryKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const m = /(.+)\_id(s?)$/.exec(k);
|
||||
if (m) {
|
||||
const subType = m[1];
|
||||
|
||||
if (m[2]) {
|
||||
const hydrated = obj[k].map((id) =>
|
||||
this._lookupSubType(subType, type, id, root)
|
||||
);
|
||||
obj[this.pluralize(subType)] = hydrated || [];
|
||||
delete obj[k];
|
||||
} else {
|
||||
const hydrated = this._lookupSubType(subType, type, obj[k], root);
|
||||
if (hydrated) {
|
||||
obj[subType] = hydrated;
|
||||
delete obj[k];
|
||||
} else {
|
||||
set(obj, subType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_hydrate(type, obj, root) {
|
||||
if (!obj) {
|
||||
throw new Error("Can't hydrate " + type + " of `null`");
|
||||
}
|
||||
|
||||
const adapter = this.adapterFor(type);
|
||||
|
||||
const id = obj[adapter.primaryKey];
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
`Can't hydrate ${type} without primaryKey: \`${adapter.primaryKey}\``
|
||||
);
|
||||
}
|
||||
|
||||
root = root || obj;
|
||||
|
||||
if (root.__rest_serializer === "1") {
|
||||
this._hydrateEmbedded(type, obj, root);
|
||||
}
|
||||
|
||||
const existing = fromMap(type, id);
|
||||
if (existing === obj) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
delete obj[adapter.primaryKey];
|
||||
let klass = this.register.lookupFactory("model:" + type);
|
||||
|
||||
if (klass && klass.class) {
|
||||
klass = klass.class;
|
||||
}
|
||||
|
||||
if (!klass) {
|
||||
klass = RestModel;
|
||||
}
|
||||
|
||||
existing.setProperties(klass.munge(obj));
|
||||
obj[adapter.primaryKey] = id;
|
||||
return existing;
|
||||
}
|
||||
|
||||
return this._build(type, obj);
|
||||
},
|
||||
});
|
||||
|
||||
export { flushMap };
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ import { deepMerge } from "discourse-common/lib/object";
|
|||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { emojiUnescape } from "discourse/lib/text";
|
||||
import { fancyTitle } from "discourse/lib/topic-fancy-title";
|
||||
import { flushMap } from "discourse/models/store";
|
||||
import { flushMap } from "discourse/services/store";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { longDate } from "discourse/lib/formatter";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
|
|
@ -8,8 +8,8 @@ import MessageBus from "message-bus-client";
|
|||
import SearchService from "discourse/services/search";
|
||||
import Session from "discourse/models/session";
|
||||
import Site from "discourse/models/site";
|
||||
import Store from "discourse/models/store";
|
||||
import User from "discourse/models/user";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
|
||||
const ALL_TARGETS = ["controller", "component", "route", "model", "adapter"];
|
||||
|
||||
|
@ -20,9 +20,6 @@ export function registerObjects(app) {
|
|||
}
|
||||
app.__registeredObjects__ = true;
|
||||
|
||||
app.register("store:main", Store);
|
||||
app.register("service:store", Store);
|
||||
|
||||
// TODO: This should be included properly
|
||||
app.register("message-bus:main", MessageBus, { instantiate: false });
|
||||
|
||||
|
@ -37,6 +34,16 @@ export default {
|
|||
initialize(container, app) {
|
||||
registerObjects(app);
|
||||
|
||||
app.register("store:main", {
|
||||
create() {
|
||||
deprecated(`"store:main" is deprecated, use "service:store" instead`, {
|
||||
since: "2.8.0.beta8",
|
||||
});
|
||||
|
||||
return container.lookup("service:store");
|
||||
},
|
||||
});
|
||||
|
||||
let siteSettings = container.lookup("site-settings:main");
|
||||
|
||||
const currentUser = User.current();
|
||||
|
|
384
app/assets/javascripts/discourse/app/services/store.js
Normal file
384
app/assets/javascripts/discourse/app/services/store.js
Normal file
|
@ -0,0 +1,384 @@
|
|||
import Service from "@ember/service";
|
||||
import { set } from "@ember/object";
|
||||
import { Promise } from "rsvp";
|
||||
import RestModel from "discourse/models/rest";
|
||||
import ResultSet from "discourse/models/result-set";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { getRegister } from "discourse-common/lib/get-owner";
|
||||
import { underscore } from "@ember/string";
|
||||
|
||||
let _identityMap;
|
||||
|
||||
// You should only call this if you're a test scaffold
|
||||
function flushMap() {
|
||||
_identityMap = {};
|
||||
}
|
||||
|
||||
function storeMap(type, id, obj) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
_identityMap[type] = _identityMap[type] || {};
|
||||
_identityMap[type][id] = obj;
|
||||
}
|
||||
|
||||
function fromMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType && byType.hasOwnProperty(id)) {
|
||||
return byType[id];
|
||||
}
|
||||
}
|
||||
|
||||
function removeMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType && byType.hasOwnProperty(id)) {
|
||||
delete byType[id];
|
||||
}
|
||||
}
|
||||
|
||||
function findAndRemoveMap(type, id) {
|
||||
const byType = _identityMap[type];
|
||||
if (byType && byType.hasOwnProperty(id)) {
|
||||
const result = byType[id];
|
||||
delete byType[id];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
flushMap();
|
||||
|
||||
export default Service.extend({
|
||||
_plurals: {
|
||||
category: "categories",
|
||||
"post-reply": "post-replies",
|
||||
"post-reply-history": "post_reply_histories",
|
||||
reviewable_history: "reviewable_histories",
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.register = this.register || getRegister(this);
|
||||
},
|
||||
|
||||
pluralize(thing) {
|
||||
return this._plurals[thing] || thing + "s";
|
||||
},
|
||||
|
||||
addPluralization(thing, plural) {
|
||||
this._plurals[thing] = plural;
|
||||
},
|
||||
|
||||
findAll(type, findArgs) {
|
||||
const adapter = this.adapterFor(type);
|
||||
|
||||
let store = this;
|
||||
return adapter.findAll(this, type, findArgs).then((result) => {
|
||||
let results = this._resultSet(type, result);
|
||||
if (adapter.afterFindAll) {
|
||||
results = adapter.afterFindAll(results, {
|
||||
lookup(subType, id) {
|
||||
return store._lookupSubType(subType, type, id, result);
|
||||
},
|
||||
});
|
||||
}
|
||||
return results;
|
||||
});
|
||||
},
|
||||
|
||||
// Mostly for legacy, things like TopicList without ResultSets
|
||||
findFiltered(type, findArgs) {
|
||||
return this.adapterFor(type)
|
||||
.find(this, type, findArgs)
|
||||
.then((result) => this._build(type, result));
|
||||
},
|
||||
|
||||
_hydrateFindResults(result, type, findArgs) {
|
||||
if (typeof findArgs === "object") {
|
||||
return this._resultSet(type, result, findArgs);
|
||||
} else {
|
||||
const apiName = this.adapterFor(type).apiNameFor(type);
|
||||
return this._hydrate(type, result[underscore(apiName)], result);
|
||||
}
|
||||
},
|
||||
|
||||
// See if the store can find stale data. We sometimes prefer to show stale data and
|
||||
// refresh it in the background.
|
||||
findStale(type, findArgs, opts) {
|
||||
const stale = this.adapterFor(type).findStale(this, type, findArgs, opts);
|
||||
return {
|
||||
hasResults: stale !== undefined,
|
||||
results: stale,
|
||||
refresh: () => this.find(type, findArgs, opts),
|
||||
};
|
||||
},
|
||||
|
||||
find(type, findArgs, opts) {
|
||||
let adapter = this.adapterFor(type);
|
||||
return adapter.find(this, type, findArgs, opts).then((result) => {
|
||||
let hydrated = this._hydrateFindResults(result, type, findArgs, opts);
|
||||
|
||||
if (result.extras) {
|
||||
hydrated.set("extras", result.extras);
|
||||
}
|
||||
|
||||
if (adapter.cache) {
|
||||
const stale = adapter.findStale(this, type, findArgs, opts);
|
||||
hydrated = this._updateStale(stale, hydrated, adapter.primaryKey);
|
||||
adapter.cacheFind(this, type, findArgs, opts, hydrated);
|
||||
}
|
||||
return hydrated;
|
||||
});
|
||||
},
|
||||
|
||||
_updateStale(stale, hydrated, primaryKey) {
|
||||
if (!stale) {
|
||||
return hydrated;
|
||||
}
|
||||
|
||||
hydrated.set(
|
||||
"content",
|
||||
hydrated.get("content").map((item) => {
|
||||
let staleItem = stale.content.findBy(primaryKey, item.get(primaryKey));
|
||||
if (staleItem) {
|
||||
staleItem.setProperties(item);
|
||||
} else {
|
||||
staleItem = item;
|
||||
}
|
||||
return staleItem;
|
||||
})
|
||||
);
|
||||
return hydrated;
|
||||
},
|
||||
|
||||
refreshResults(resultSet, type, url) {
|
||||
const adapter = this.adapterFor(type);
|
||||
return ajax(url).then((result) => {
|
||||
const typeName = underscore(this.pluralize(adapter.apiNameFor(type)));
|
||||
const content = result[typeName].map((obj) =>
|
||||
this._hydrate(type, obj, result)
|
||||
);
|
||||
resultSet.set("content", content);
|
||||
});
|
||||
},
|
||||
|
||||
appendResults(resultSet, type, url) {
|
||||
const adapter = this.adapterFor(type);
|
||||
return ajax(url).then((result) => {
|
||||
const typeName = underscore(this.pluralize(adapter.apiNameFor(type)));
|
||||
|
||||
let pageTarget = result.meta || result;
|
||||
let totalRows =
|
||||
pageTarget["total_rows_" + typeName] || resultSet.get("totalRows");
|
||||
let loadMoreUrl = pageTarget["load_more_" + typeName];
|
||||
let content = result[typeName].map((obj) =>
|
||||
this._hydrate(type, obj, result)
|
||||
);
|
||||
|
||||
resultSet.setProperties({ totalRows, loadMoreUrl });
|
||||
resultSet.get("content").pushObjects(content);
|
||||
|
||||
// If we've loaded them all, clear the load more URL
|
||||
if (resultSet.get("length") >= totalRows) {
|
||||
resultSet.set("loadMoreUrl", null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
update(type, id, attrs) {
|
||||
const adapter = this.adapterFor(type);
|
||||
return adapter.update(this, type, id, attrs, function (result) {
|
||||
if (result && result[type] && result[type][adapter.primaryKey]) {
|
||||
const oldRecord = findAndRemoveMap(type, id);
|
||||
storeMap(type, result[type][adapter.primaryKey], oldRecord);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
createRecord(type, attrs) {
|
||||
attrs = attrs || {};
|
||||
const adapter = this.adapterFor(type);
|
||||
return !!attrs[adapter.primaryKey]
|
||||
? this._hydrate(type, attrs)
|
||||
: this._build(type, attrs);
|
||||
},
|
||||
|
||||
destroyRecord(type, record) {
|
||||
const adapter = this.adapterFor(type);
|
||||
|
||||
// If the record is new, don't perform an Ajax call
|
||||
if (record.get("isNew")) {
|
||||
removeMap(type, record.get(adapter.primaryKey));
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
return adapter.destroyRecord(this, type, record).then(function (result) {
|
||||
removeMap(type, record.get(adapter.primaryKey));
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
_resultSet(type, result, findArgs) {
|
||||
const adapter = this.adapterFor(type);
|
||||
const typeName = underscore(this.pluralize(adapter.apiNameFor(type)));
|
||||
|
||||
if (!result[typeName]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`JSON response is missing \`${typeName}\` key`, result);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = result[typeName].map((obj) =>
|
||||
this._hydrate(type, obj, result)
|
||||
);
|
||||
|
||||
let pageTarget = result.meta || result;
|
||||
|
||||
const createArgs = {
|
||||
content,
|
||||
findArgs,
|
||||
totalRows: pageTarget["total_rows_" + typeName] || content.length,
|
||||
loadMoreUrl: pageTarget["load_more_" + typeName],
|
||||
refreshUrl: pageTarget["refresh_" + typeName],
|
||||
resultSetMeta: result.meta,
|
||||
store: this,
|
||||
__type: type,
|
||||
};
|
||||
|
||||
if (result.extras) {
|
||||
createArgs.extras = result.extras;
|
||||
}
|
||||
|
||||
return ResultSet.create(createArgs);
|
||||
},
|
||||
|
||||
_build(type, obj) {
|
||||
const adapter = this.adapterFor(type);
|
||||
obj.store = this;
|
||||
obj.__type = type;
|
||||
obj.__state = obj[adapter.primaryKey] ? "created" : "new";
|
||||
|
||||
// TODO: Have injections be automatic
|
||||
obj.topicTrackingState = this.register.lookup("topic-tracking-state:main");
|
||||
obj.keyValueStore = this.register.lookup("key-value-store:main");
|
||||
|
||||
const klass = this.register.lookupFactory("model:" + type) || RestModel;
|
||||
const model = klass.create(obj);
|
||||
|
||||
storeMap(type, obj[adapter.primaryKey], model);
|
||||
return model;
|
||||
},
|
||||
|
||||
adapterFor(type) {
|
||||
return (
|
||||
this.register.lookup("adapter:" + type) ||
|
||||
this.register.lookup("adapter:rest")
|
||||
);
|
||||
},
|
||||
|
||||
_lookupSubType(subType, type, id, root) {
|
||||
if (root.meta && root.meta.types) {
|
||||
subType = root.meta.types[subType] || subType;
|
||||
}
|
||||
|
||||
const subTypeAdapter = this.adapterFor(subType);
|
||||
const pluralType = this.pluralize(subType);
|
||||
const collection = root[this.pluralize(subType)];
|
||||
if (collection) {
|
||||
const hashedProp = "__hashed_" + pluralType;
|
||||
let hashedCollection = root[hashedProp];
|
||||
if (!hashedCollection) {
|
||||
hashedCollection = {};
|
||||
collection.forEach(function (it) {
|
||||
hashedCollection[it[subTypeAdapter.primaryKey]] = it;
|
||||
});
|
||||
root[hashedProp] = hashedCollection;
|
||||
}
|
||||
|
||||
const found = hashedCollection[id];
|
||||
if (found) {
|
||||
const hydrated = this._hydrate(subType, found, root);
|
||||
hashedCollection[id] = hydrated;
|
||||
return hydrated;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_hydrateEmbedded(type, obj, root) {
|
||||
const adapter = this.adapterFor(type);
|
||||
Object.keys(obj).forEach((k) => {
|
||||
if (k === adapter.primaryKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const m = /(.+)\_id(s?)$/.exec(k);
|
||||
if (m) {
|
||||
const subType = m[1];
|
||||
|
||||
if (m[2]) {
|
||||
const hydrated = obj[k].map((id) =>
|
||||
this._lookupSubType(subType, type, id, root)
|
||||
);
|
||||
obj[this.pluralize(subType)] = hydrated || [];
|
||||
delete obj[k];
|
||||
} else {
|
||||
const hydrated = this._lookupSubType(subType, type, obj[k], root);
|
||||
if (hydrated) {
|
||||
obj[subType] = hydrated;
|
||||
delete obj[k];
|
||||
} else {
|
||||
set(obj, subType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_hydrate(type, obj, root) {
|
||||
if (!obj) {
|
||||
throw new Error("Can't hydrate " + type + " of `null`");
|
||||
}
|
||||
|
||||
const adapter = this.adapterFor(type);
|
||||
|
||||
const id = obj[adapter.primaryKey];
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
`Can't hydrate ${type} without primaryKey: \`${adapter.primaryKey}\``
|
||||
);
|
||||
}
|
||||
|
||||
root = root || obj;
|
||||
|
||||
if (root.__rest_serializer === "1") {
|
||||
this._hydrateEmbedded(type, obj, root);
|
||||
}
|
||||
|
||||
const existing = fromMap(type, id);
|
||||
if (existing === obj) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
delete obj[adapter.primaryKey];
|
||||
let klass = this.register.lookupFactory("model:" + type);
|
||||
|
||||
if (klass && klass.class) {
|
||||
klass = klass.class;
|
||||
}
|
||||
|
||||
if (!klass) {
|
||||
klass = RestModel;
|
||||
}
|
||||
|
||||
existing.setProperties(klass.munge(obj));
|
||||
obj[adapter.primaryKey] = id;
|
||||
return existing;
|
||||
}
|
||||
|
||||
return this._build(type, obj);
|
||||
},
|
||||
});
|
||||
|
||||
export { flushMap };
|
|
@ -1,6 +1,6 @@
|
|||
import KeyValueStore from "discourse/lib/key-value-store";
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
import Store from "discourse/models/store";
|
||||
import Store from "discourse/services/store";
|
||||
import TopicListAdapter from "discourse/adapters/topic-list";
|
||||
import TopicTrackingState from "discourse/models/topic-tracking-state";
|
||||
import { buildResolver } from "discourse-common/resolver";
|
||||
|
|
|
@ -21,7 +21,7 @@ import { _clearSnapshots } from "select-kit/components/composer-actions";
|
|||
import { clearHTMLCache } from "discourse/helpers/custom-html";
|
||||
import createStore from "discourse/tests/helpers/create-store";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import { flushMap } from "discourse/models/store";
|
||||
import { flushMap } from "discourse/services/store";
|
||||
import { initSearchData } from "discourse/widgets/search-menu";
|
||||
import { resetPostMenuExtraButtons } from "discourse/widgets/post-menu";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
|
|
|
@ -27,7 +27,7 @@ import bootbox from "bootbox";
|
|||
import { buildResolver } from "discourse-common/resolver";
|
||||
import { createHelperContext } from "discourse-common/lib/helpers";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import { flushMap } from "discourse/models/store";
|
||||
import { flushMap } from "discourse/services/store";
|
||||
import { registerObjects } from "discourse/pre-initializers/inject-discourse-objects";
|
||||
import sinon from "sinon";
|
||||
import { run } from "@ember/runloop";
|
||||
|
|
Loading…
Reference in New Issue
Block a user