Skip to content

Commit ab0ea09

Browse files
committed
Schema.js cleanup (#1540)
* Tidy up schemas router * de-duplicate logic for injecting default schemas * Remove no-op promise * use hasClass * Make getAllSchemas part of SchemaController * Move getOneSchema logic onto schema controller * remove log
1 parent 414ee67 commit ab0ea09

File tree

5 files changed

+103
-104
lines changed

5 files changed

+103
-104
lines changed

src/Adapters/Storage/Mongo/MongoSchemaCollection.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
import MongoCollection from './MongoCollection';
3-
import * as transform from './MongoTransform';
3+
import * as transform from './MongoTransform';
44

55
function mongoFieldToParseSchemaField(type) {
66
if (type[0] === '*') {
@@ -128,18 +128,12 @@ class MongoSchemaCollection {
128128
this._collection = collection;
129129
}
130130

131-
// Return a promise for all schemas known to this adapter, in Parse format. In case the
132-
// schemas cannot be retrieved, returns a promise that rejects. Requirements fot the
133-
// rejection reason are TBD.
134-
getAllSchemas() {
131+
_fetchAllSchemasFrom_SCHEMA() {
135132
return this._collection._rawFind({})
136133
.then(schemas => schemas.map(mongoSchemaToParseSchema));
137134
}
138135

139-
// Return a promise for the schema with the given name, in Parse format. If
140-
// this adapter doesn't know about the schema, return a promise that rejects with
141-
// undefined as the reason.
142-
findSchema(name: string) {
136+
_fechOneSchemaFrom_SCHEMA(name: string) {
143137
return this._collection._rawFind(_mongoSchemaQueryFromNameQuery(name), { limit: 1 }).then(results => {
144138
if (results.length === 1) {
145139
return mongoSchemaToParseSchema(results[0]);

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ export class MongoStorageAdapter {
127127
.then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate));
128128
}
129129

130+
// Return a promise for all schemas known to this adapter, in Parse format. In case the
131+
// schemas cannot be retrieved, returns a promise that rejects. Requirements for the
132+
// rejection reason are TBD.
133+
getAllSchemas() {
134+
return this.schemaCollection().then(schemasCollection => schemasCollection._fetchAllSchemasFrom_SCHEMA());
135+
}
136+
137+
// Return a promise for the schema with the given name, in Parse format. If
138+
// this adapter doesn't know about the schema, return a promise that rejects with
139+
// undefined as the reason.
140+
getOneSchema(className) {
141+
return this.schemaCollection().then(schemasCollection => schemasCollection._fechOneSchemaFrom_SCHEMA(className));
142+
}
143+
130144
// TODO: As yet not particularly well specified. Creates an object. Does it really need the schema?
131145
// or can it fetch the schema itself? Also the schema is not currently a Parse format schema, and it
132146
// should be, if we are passing it at all.

src/Controllers/DatabaseController.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ DatabaseController.prototype.loadSchema = function(acceptor = () => true) {
6767
if (!this.schemaPromise) {
6868
this.schemaPromise = this.schemaCollection().then(collection => {
6969
delete this.schemaPromise;
70-
return Schema.load(collection);
70+
return Schema.load(collection, this.adapter);
7171
});
7272
return this.schemaPromise;
7373
}
@@ -78,7 +78,7 @@ DatabaseController.prototype.loadSchema = function(acceptor = () => true) {
7878
}
7979
this.schemaPromise = this.schemaCollection().then(collection => {
8080
delete this.schemaPromise;
81-
return Schema.load(collection);
81+
return Schema.load(collection, this.adapter);
8282
});
8383
return this.schemaPromise;
8484
});

src/Routers/SchemasRouter.js

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,24 @@ function classNameMismatchResponse(bodyClass, pathClass) {
1414
);
1515
}
1616

17-
function injectDefaultSchema(schema) {
18-
let defaultSchema = Schema.defaultColumns[schema.className];
19-
if (defaultSchema) {
20-
Object.keys(defaultSchema).forEach((key) => {
21-
schema.fields[key] = defaultSchema[key];
22-
});
23-
}
24-
return schema;
25-
}
26-
2717
function getAllSchemas(req) {
28-
return req.config.database.schemaCollection()
29-
.then(collection => collection.getAllSchemas())
30-
.then(schemas => schemas.map(injectDefaultSchema))
31-
.then(schemas => ({ response: { results: schemas } }));
18+
return req.config.database.loadSchema()
19+
.then(schemaController => schemaController.getAllSchemas())
20+
.then(schemas => ({ response: { results: schemas } }));
3221
}
3322

3423
function getOneSchema(req) {
3524
const className = req.params.className;
36-
return req.config.database.schemaCollection()
37-
.then(collection => collection.findSchema(className))
38-
.then(injectDefaultSchema)
39-
.then(schema => ({ response: schema }))
40-
.catch(error => {
41-
if (error === undefined) {
42-
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
43-
} else {
44-
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.');
45-
}
46-
});
25+
return req.config.database.loadSchema()
26+
.then(schemaController => schemaController.getOneSchema(className))
27+
.then(schema => ({ response: schema }))
28+
.catch(error => {
29+
if (error === undefined) {
30+
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
31+
} else {
32+
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.');
33+
}
34+
});
4735
}
4836

4937
function createSchema(req) {
@@ -72,19 +60,8 @@ function modifySchema(req) {
7260
let className = req.params.className;
7361

7462
return req.config.database.loadSchema()
75-
.then(schema => {
76-
return schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database);
77-
}).then((result) => {
78-
return Promise.resolve({response: result});
79-
});
80-
}
81-
82-
function getSchemaPermissions(req) {
83-
var className = req.params.className;
84-
return req.config.database.loadSchema()
85-
.then(schema => {
86-
return Promise.resolve({response: schema.perms[className]});
87-
});
63+
.then(schema => schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database))
64+
.then(result => ({response: result}));
8865
}
8966

9067
// A helper function that removes all join tables for a schema. Returns a promise.

src/Schema.js

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,27 @@ const fieldTypeIsInvalid = ({ type, targetClass }) => {
201201
return undefined;
202202
}
203203

204+
const injectDefaultSchema = schema => ({
205+
className: schema.className,
206+
fields: {
207+
...defaultColumns._Default,
208+
...(defaultColumns[schema.className] || {}),
209+
...schema.fields,
210+
},
211+
classLevelPermissions: schema.classLevelPermissions,
212+
})
213+
204214
// Stores the entire schema of the app in a weird hybrid format somewhere between
205215
// the mongo format and the Parse format. Soon, this will all be Parse format.
206-
class Schema {
216+
class SchemaController {
207217
_collection;
218+
_dbAdapter;
208219
data;
209220
perms;
210221

211-
constructor(collection) {
222+
constructor(collection, databaseAdapter) {
212223
this._collection = collection;
224+
this._dbAdapter = databaseAdapter;
213225

214226
// this.data[className][fieldName] tells you the type of that field, in mongo format
215227
this.data = {};
@@ -220,19 +232,25 @@ class Schema {
220232
reloadData() {
221233
this.data = {};
222234
this.perms = {};
223-
return this._collection.getAllSchemas().then(allSchemas => {
235+
return this.getAllSchemas()
236+
.then(allSchemas => {
224237
allSchemas.forEach(schema => {
225-
const parseFormatSchema = {
226-
...defaultColumns._Default,
227-
...(defaultColumns[schema.className] || {}),
228-
...schema.fields,
229-
}
230-
this.data[schema.className] = parseFormatSchema;
238+
this.data[schema.className] = schema.fields;
231239
this.perms[schema.className] = schema.classLevelPermissions;
232240
});
233241
});
234242
}
235243

244+
getAllSchemas() {
245+
return this._dbAdapter.getAllSchemas()
246+
.then(allSchemas => allSchemas.map(injectDefaultSchema));
247+
}
248+
249+
getOneSchema(className) {
250+
return this._dbAdapter.getOneSchema(className)
251+
.then(injectDefaultSchema);
252+
}
253+
236254
// Create a new class that includes the three default fields.
237255
// ACL is an implicit column that does not get an entry in the
238256
// _SCHEMAS database. Returns a promise that resolves with the
@@ -247,9 +265,6 @@ class Schema {
247265
}
248266

249267
return this._collection.addSchema(className, fields, classLevelPermissions)
250-
.then(res => {
251-
return Promise.resolve(res);
252-
})
253268
.catch(error => {
254269
if (error === undefined) {
255270
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
@@ -260,40 +275,42 @@ class Schema {
260275
}
261276

262277
updateClass(className, submittedFields, classLevelPermissions, database) {
263-
if (!this.data[className]) {
264-
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
265-
}
266-
let existingFields = Object.assign(this.data[className], {_id: className});
267-
Object.keys(submittedFields).forEach(name => {
268-
let field = submittedFields[name];
269-
if (existingFields[name] && field.__op !== 'Delete') {
270-
throw new Parse.Error(255, `Field ${name} exists, cannot update.`);
271-
}
272-
if (!existingFields[name] && field.__op === 'Delete') {
273-
throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`);
278+
return this.hasClass(className)
279+
.then(hasClass => {
280+
if (!hasClass) {
281+
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
274282
}
275-
});
276-
277-
let newSchema = buildMergedSchemaObject(existingFields, submittedFields);
278-
let validationError = this.validateSchemaData(className, newSchema, classLevelPermissions);
279-
if (validationError) {
280-
throw new Parse.Error(validationError.code, validationError.error);
281-
}
283+
let existingFields = Object.assign(this.data[className], {_id: className});
284+
Object.keys(submittedFields).forEach(name => {
285+
let field = submittedFields[name];
286+
if (existingFields[name] && field.__op !== 'Delete') {
287+
throw new Parse.Error(255, `Field ${name} exists, cannot update.`);
288+
}
289+
if (!existingFields[name] && field.__op === 'Delete') {
290+
throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`);
291+
}
292+
});
282293

283-
// Finally we have checked to make sure the request is valid and we can start deleting fields.
284-
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
285-
let deletePromises = [];
286-
let insertedFields = [];
287-
Object.keys(submittedFields).forEach(fieldName => {
288-
if (submittedFields[fieldName].__op === 'Delete') {
289-
const promise = this.deleteField(fieldName, className, database);
290-
deletePromises.push(promise);
291-
} else {
292-
insertedFields.push(fieldName);
294+
let newSchema = buildMergedSchemaObject(existingFields, submittedFields);
295+
let validationError = this.validateSchemaData(className, newSchema, classLevelPermissions);
296+
if (validationError) {
297+
throw new Parse.Error(validationError.code, validationError.error);
293298
}
294-
});
295299

296-
return Promise.all(deletePromises) // Delete Everything
300+
// Finally we have checked to make sure the request is valid and we can start deleting fields.
301+
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
302+
let deletePromises = [];
303+
let insertedFields = [];
304+
Object.keys(submittedFields).forEach(fieldName => {
305+
if (submittedFields[fieldName].__op === 'Delete') {
306+
const promise = this.deleteField(fieldName, className, database);
307+
deletePromises.push(promise);
308+
} else {
309+
insertedFields.push(fieldName);
310+
}
311+
});
312+
313+
return Promise.all(deletePromises) // Delete Everything
297314
.then(() => this.reloadData()) // Reload our Schema, so we have all the new values
298315
.then(() => {
299316
let promises = insertedFields.map(fieldName => {
@@ -302,15 +319,14 @@ class Schema {
302319
});
303320
return Promise.all(promises);
304321
})
305-
.then(() => {
306-
return this.setPermissions(className, classLevelPermissions)
307-
})
322+
.then(() => this.setPermissions(className, classLevelPermissions))
308323
//TODO: Move this logic into the database adapter
309-
.then(() => {
310-
return { className: className,
311-
fields: this.data[className],
312-
classLevelPermissions: this.perms[className] }
313-
});
324+
.then(() => ({
325+
className: className,
326+
fields: this.data[className],
327+
classLevelPermissions: this.perms[className]
328+
}));
329+
})
314330
}
315331

316332

@@ -637,9 +653,7 @@ class Schema {
637653
return undefined;
638654
};
639655

640-
// Checks if a given class is in the schema. Needs to load the
641-
// schema first, which is kinda janky. Hopefully we can refactor
642-
// and make this be a regular value.
656+
// Checks if a given class is in the schema.
643657
hasClass(className) {
644658
return this.reloadData().then(() => !!(this.data[className]));
645659
}
@@ -672,8 +686,8 @@ class Schema {
672686
}
673687

674688
// Returns a promise for a new Schema.
675-
function load(collection) {
676-
let schema = new Schema(collection);
689+
function load(collection, dbAdapter) {
690+
let schema = new SchemaController(collection, dbAdapter);
677691
return schema.reloadData().then(() => schema);
678692
}
679693

0 commit comments

Comments
 (0)