Merge pull request #106 from wistityhq/feature/migrations

Migrations (alpha)
This commit is contained in:
Loïc Saint-Roch 2016-04-22 11:14:32 +02:00
commit d17d48db0b
17 changed files with 832 additions and 247 deletions

View File

@ -9,6 +9,7 @@ const _ = require('lodash');
// Strapi helper for GraphQL. // Strapi helper for GraphQL.
const helpers = require('strapi/lib/configuration/hooks/graphql/helpers/'); const helpers = require('strapi/lib/configuration/hooks/graphql/helpers/');
const utils = require('./');
/** /**
* Utils functions for BookShelf * Utils functions for BookShelf
@ -132,6 +133,8 @@ module.exports = {
update: function (collectionIdentity, rootValue, args) { update: function (collectionIdentity, rootValue, args) {
_.merge(args, rootValue.context.request.body); _.merge(args, rootValue.context.request.body);
const PK = utils.getPK(collectionIdentity.toLowerCase(), null, strapi.models);
return strapi.services[collectionIdentity.toLowerCase()] return strapi.services[collectionIdentity.toLowerCase()]
.edit(_.set({}, PK, args[PK]), _.omit(args, PK)) .edit(_.set({}, PK, args[PK]), _.omit(args, PK))
.then(function (data) { .then(function (data) {

View File

@ -5,12 +5,17 @@
*/ */
// Node.js core. // Node.js core.
const _ = require('lodash');
const async = require('async');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
// Public node modules. // Public node modules.
const beautify = require('js-beautify').js_beautify; const beautify = require('js-beautify').js_beautify;
// Local utilities.
const dictionary = require('strapi-utils/lib/dictionary');
/** /**
* Runs after this generator has finished * Runs after this generator has finished
* *
@ -19,6 +24,8 @@ const beautify = require('js-beautify').js_beautify;
*/ */
module.exports = function afterGenerate(scope, cb) { module.exports = function afterGenerate(scope, cb) {
async.parallel({
migrationFile: function (cb) {
const migrationFile = path.resolve(scope.rootPath, 'data', 'migrations', scope.connection, scope.filename); const migrationFile = path.resolve(scope.rootPath, 'data', 'migrations', scope.connection, scope.filename);
// Read the migration file. // Read the migration file.
@ -33,6 +40,54 @@ module.exports = function afterGenerate(scope, cb) {
keep_function_indentation: true, keep_function_indentation: true,
space_before_conditional: true, space_before_conditional: true,
end_with_newline: true end_with_newline: true
}), 'utf8', function (err) {
if (err) {
return cb(err, null);
} else {
return cb(null, null);
}
});
});
},
settings: function (cb) {
dictionary.aggregate({
dirname: path.resolve(scope.rootPath, 'api'),
filter: /(.+)\.settings.json$/,
depth: 4
}, cb);
},
functions: function (cb) {
dictionary.aggregate({
dirname: path.resolve(scope.rootPath, 'api'),
filter: /(.+)\.js$/,
depth: 4
}, cb);
}
}, function (err, data) {
if (err) {
return cb.invalid(err);
}
// Fetch all models
const models = _.get(_.merge(data.settings, data.functions), 'models');
if (!_.isUndefined(models)) {
_.mapValues(models, function (model) {
return _.omitBy(model, _.isFunction);
});
const modelsKeyLowercased = _.mapKeys(models, function (model, key) {
return key.toLowerCase();
});
const historyFile = path.resolve(scope.rootPath, 'data', 'migrations', '.history');
// And rewrite it with the beautify node module.
fs.writeFile(historyFile, beautify(JSON.stringify(modelsKeyLowercased), {
indent_size: 2,
keep_function_indentation: true,
space_before_conditional: true,
end_with_newline: true
}), 'utf8', function (err) { }), 'utf8', function (err) {
if (err) { if (err) {
return cb.invalid(err); return cb.invalid(err);
@ -40,5 +95,6 @@ module.exports = function afterGenerate(scope, cb) {
return cb.success(); return cb.success();
} }
}); });
}
}); });
}; };

View File

@ -76,23 +76,36 @@ module.exports = function (scope, cb) {
} }
}); });
const history = (function () {
try {
return JSON.parse(fs.readFileSync(path.resolve(scope.rootPath, 'data', 'migrations', '.history'), 'utf8'));
} catch (err) {
// File not existing
return {};
}
})();
// Register every model. // Register every model.
const migrations = glob.sync(path.resolve(scope.rootPath, 'api', '**', 'models', '*.json')).map((file) => { const migrations = glob.sync(path.resolve(scope.rootPath, 'api', '**', 'models', '*.json')).map((filepath) => {
let modelName; try {
const file = JSON.parse(fs.readFileSync(path.resolve(filepath)));
// Only create migration file for the models with the specified connection. // Only create migration file for the models with the specified connection.
if (JSON.parse(fs.readFileSync(path.resolve(file))).connection === scope.connection) { if (_.get(file, 'connection') === _.get(scope, 'connection')) {
// Save the model name thanks to the given table name. // Save the model name thanks to the given table name.
modelName = JSON.parse(fs.readFileSync(path.resolve(file))).tableName; const modelName = _.get(file, 'tableName');
scope.models[modelName] = JSON.parse(fs.readFileSync(path.resolve(file))); scope.models[modelName] = file;
if (!_.isEmpty(history) && history.hasOwnProperty(modelName)) {
_.set(scope.models, modelName + '.oldAttributes', _.get(history, modelName + '.attributes'));
} else {
_.set(scope.models, modelName + '.oldAttributes', {});
}
// First, we need to know if the table already exists. // First, we need to know if the table already exists.
scope.db.schema.hasTable(modelName).then(function (exists) { scope.db.schema.hasTable(modelName).then(function (exists) {
// If the table doesn't exist. // If the table doesn't exist.
if (!exists) { if (!exists) {
// Builder: add needed options specified in the model // Builder: add needed options specified in the model
// for each option. // for each option.
_.forEach(scope.models[modelName].options, function (value, option) { _.forEach(scope.models[modelName].options, function (value, option) {
@ -111,26 +124,41 @@ module.exports = function (scope, cb) {
// Builder: create and drop the table. // Builder: create and drop the table.
builder.createTable(scope.models, modelName); builder.createTable(scope.models, modelName);
} } else {
// If the table already exists. // If the table already exists.
else {
// Ideally, we need to verify the table properties here // Set new attributes object
// to see if they still are the same. _.set(scope.models[modelName], 'newAttributes', {});
// Parse every attribute. // Identity added, updated and removed attributes
_.forEach(scope.models[modelName].attributes, function (details, attribute) { const attributesRemoved = _.difference(_.keys(scope.models[modelName].oldAttributes), _.keys(scope.models[modelName].attributes));
const attributesAddedOrUpdated = _.difference(_.keys(scope.models[modelName].attributes), attributesRemoved);
// Verify if a column already exists for the attribute. // Parse every attribute which has been removed.
scope.db.schema.hasColumn(modelName, attribute).then(function (exists) { _.forEach(attributesRemoved, function (attribute) {
scope.models[modelName].newAttributes = {}; const details = scope.models[modelName].oldAttributes[attribute];
details.isRemoved = true;
// If it's a new attribute.
if (!exists) {
// Save the attribute as a new attribute. // Save the attribute as a new attribute.
scope.models[modelName].newAttributes[attribute] = details; scope.models[modelName].newAttributes[attribute] = _.cloneDeep(details);
// Builder: create template for each attribute-- either with a column type
// or with a relationship.
if (details.type && _.isString(details.type)) {
builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, true, true);
} else if (_.isString(details.collection) || _.isString(details.model)) {
builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, true, true, history);
}
});
// Parse every attribute which has been added or updated.
_.forEach(attributesAddedOrUpdated, function (attribute) {
const details = scope.models[modelName].attributes[attribute];
// If it's a new attribute.
if (!scope.models[modelName].oldAttributes.hasOwnProperty(attribute)) {
// Save the attribute as a new attribute.
scope.models[modelName].newAttributes[attribute] = _.cloneDeep(details);
// Builder: create template for each attribute-- either with a column type // Builder: create template for each attribute-- either with a column type
// or with a relationship. // or with a relationship.
@ -139,30 +167,72 @@ module.exports = function (scope, cb) {
} else if (_.isString(details.collection) || _.isString(details.model)) { } else if (_.isString(details.collection) || _.isString(details.model)) {
builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute); builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute);
} }
} else {
// If it's an existing attribute.
// Try to identify attribute updates
const toDrop = (function () {
if (details.hasOwnProperty('collection') && details.hasOwnProperty('via') &&
(_.get(scope.models[modelName].oldAttributes[attribute], 'collection') !== details.collection || _.get(scope.models[modelName].oldAttributes[attribute], 'via') !== details.via)) {
return true;
} else if (details.hasOwnProperty('model') && details.hasOwnProperty('via') &&
(_.get(scope.models[modelName].oldAttributes[attribute], 'model') !== details.model || _.get(scope.models[modelName].oldAttributes[attribute], 'via') !== details.via)) {
return true;
} else if (details.hasOwnProperty('model') &&
(_.get(scope.models[modelName].oldAttributes[attribute], 'model') !== details.model)) {
return true;
} else if (details.hasOwnProperty('model') && !_.get(scope.models[modelName].oldAttributes, attribute).hasOwnProperty('model')) {
return true;
} else if (details.hasOwnProperty('collection') && !_.get(scope.models[modelName].oldAttributes, attribute).hasOwnProperty('collection')) {
return true;
} else if (details.hasOwnProperty('via') && !_.get(scope.models[modelName].oldAttributes, attribute).hasOwnProperty('via')) {
return true;
} else if (!_.isUndefined(details.type) && _.get(scope.models[modelName].oldAttributes[attribute], 'type') !== _.get(details, 'type')) {
return true;
} else if (!_.isUndefined(details.defaultValue) && _.get(scope.models[modelName].oldAttributes[attribute], 'defaultValue') === _.get(details, 'defaultValue')) {
return true;
} else if (!_.isUndefined(details.maxLength) && _.get(scope.models[modelName].oldAttributes[attribute], 'maxLength') === _.get(details, 'maxLength')) {
return true;
} else if (!_.isUndefined(details.nullable) && _.get(scope.models[modelName].oldAttributes[attribute], 'nullable') === _.get(details, 'nullable')) {
return true;
} else {
return false;
}
})();
// The attribute has been updated.
// We will drop it then create it again with the new options.
if (toDrop) {
// Save the attribute as a new attribute.
scope.models[modelName].newAttributes[attribute] = _.cloneDeep(details);
// Builder: create template for each attribute-- either with a column type
// or with a relationship.
if (details.type && _.isString(details.type)) {
builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, true);
} else if (_.isString(details.collection) || _.isString(details.model)) {
builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, true, false, history);
}
}
}
});
// For lightweight migration file,
// only call this when new attributes are detected.
if (!_.isEmpty(scope.models[modelName].newAttributes)) {
// Builder: select the table. // Builder: select the table.
builder.selectTable(scope.models, modelName); builder.selectTable(scope.models, modelName);
} }
// If the column already exists.
else {
// TODO: Verify columns info are the same.
// scope.db(modelName).columnInfo(attribute).then(function (info) {
//
// });
}
}).catch(function (err) {
console.log(err);
});
});
} }
}); });
return new Promise((resolve) => { return new Promise((resolve) => {
asyncFunction(file, resolve); asyncFunction(filepath, resolve);
}); });
} }
} catch (e) {
return cb.invalid(e);
}
}); });
function asyncFunction(item, cb) { function asyncFunction(item, cb) {

View File

@ -23,19 +23,35 @@ module.exports = function (models, modelName) {
// Then, every `up` logic of every model call the // Then, every `up` logic of every model call the
// `./builder/tables/createTableIfNotExists` template. // `./builder/tables/createTableIfNotExists` template.
const tplTableCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'createTableIfNotExists.template'), 'utf8'); const tplTableCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'createTableIfNotExists.template'), 'utf8');
models[modelName].up = _.unescape(_.template(tplTableCreate)({ if (_.isEmpty(_.get(models[modelName], 'up.others'))) {
_.set(models[modelName], 'up.others', _.unescape(_.template(tplTableCreate)({
models: models,
tableName: modelName,
attributes: models[modelName].attributes,
options: models[modelName].options
})));
} else {
models[modelName].up.others += _.unescape(_.template(tplTableCreate)({
models: models, models: models,
tableName: modelName, tableName: modelName,
attributes: models[modelName].attributes, attributes: models[modelName].attributes,
options: models[modelName].options options: models[modelName].options
})); }));
}
// Template: drop the table for the `down` export. // Template: drop the table for the `down` export.
// This adds a `down` logic for the current model. // This adds a `down` logic for the current model.
// Then, every `down` logic of every model call the // Then, every `down` logic of every model call the
// `./builder/tables/dropTable` template. // `./builder/tables/dropTable` template.
const tplTableDrop = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'dropTable.template'), 'utf8'); const tplTableDrop = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'dropTable.template'), 'utf8');
models[modelName].down = _.unescape(_.template(tplTableDrop)({
if (_.isEmpty(_.get(models[modelName], 'down.others'))) {
_.set(models[modelName], 'down.others', _.unescape(_.template(tplTableDrop)({
tableName: modelName
})));
} else {
models[modelName].down.others += _.unescape(_.template(tplTableDrop)({
tableName: modelName tableName: modelName
})); }));
}
}; };

View File

@ -12,144 +12,384 @@ const path = require('path');
const _ = require('lodash'); const _ = require('lodash');
const pluralize = require('pluralize'); const pluralize = require('pluralize');
// Bookshelf utils. // Collections utils.
const utilsModels = require('strapi/lib/configuration/hooks/models/utils/'); const utilsModels = require('strapi/lib/configuration/hooks/models/utils/');
const utilsBookShelf = require('strapi-bookshelf/lib/utils/'); const utilsBookShelf = require('strapi-bookshelf/lib/utils/');
// Template builder.
const selectTable = require('./selectTable');
/** /**
* Relationship templates * Relationship templates
*/ */
module.exports = function (models, modelName, details, attribute) { module.exports = function (rootModels, modelName, details, attribute, toDrop, onlyDrop, history) {
let tplRelationUp; let tplRelationUp;
let tplRelationDown; let tplRelationDown;
let infos = {};
let oldInfos = {};
const infos = utilsModels.getNature(details, attribute, models); if (!onlyDrop && toDrop) {
infos = utilsModels.getNature(details, attribute, rootModels);
oldInfos = utilsModels.getNature(_.get(rootModels[modelName].oldAttributes, attribute), attribute, history);
const isDifferentVerbose = !(oldInfos.hasOwnProperty('nature') && oldInfos.nature === infos.nature);
if (isDifferentVerbose) {
handleRelation(oldInfos, history, modelName, _.get(rootModels[modelName].oldAttributes, attribute), attribute, true, true);
handleRelation(infos, rootModels, modelName, details, attribute);
} else {
handleRelation(infos, rootModels, modelName, details, attribute, true, true);
}
} else if (onlyDrop || toDrop) {
oldInfos = utilsModels.getNature(_.get(rootModels[modelName].oldAttributes, attribute), attribute, history);
handleRelation(oldInfos, history, modelName, _.get(rootModels[modelName].oldAttributes, attribute), attribute, true, true);
} else {
infos = utilsModels.getNature(details, attribute, rootModels);
handleRelation(infos, rootModels, modelName, details, attribute);
}
function handleRelation(infos, models, modelName, details, attribute, toDrop, onlyDrop) {
if (_.isEmpty(_.get(rootModels[modelName].attributes, attribute + '.create'))) {
_.set(rootModels[modelName].attributes, attribute + '.create', {
drop: '',
others: ''
});
}
if (_.isEmpty(_.get(rootModels[modelName].attributes, attribute + '.delete'))) {
_.set(rootModels[modelName].attributes, attribute + '.delete', {
drop: '',
others: ''
});
}
// If it's a "one-to-one" relationship. // If it's a "one-to-one" relationship.
if (infos.verbose === 'hasOne') { if (infos.verbose === 'hasOne') {
// Force singular foreign key.
// Force singular foreign key
details.attribute = pluralize.singular(details.model); details.attribute = pluralize.singular(details.model);
// Define PK column // Define PK column.
details.column = utilsBookShelf.getPK(modelName, undefined, models); details.column = utilsBookShelf.getPK(modelName, undefined, models);
// Template: create a new column thanks to the attribute's relation. if (!toDrop) {
// Simply make a `create` template for this attribute wich will be added
// to the table template-- either `./builder/tables/selectTable` or
// `./builder/tables/createTableIfNotExists`.
tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'hasOne.template'), 'utf8'); tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'hasOne.template'), 'utf8');
models[modelName].attributes[attribute].create = _.unescape(_.template(tplRelationUp)({ models[modelName].attributes[attribute].create.others += _.unescape(_.template(tplRelationUp)({
tableName: modelName, tableName: modelName,
attribute: attribute, attribute: attribute,
details: details details: details
})); }));
// Template: drop the column. tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn-unique.template'), 'utf8');
// Simply make a `delete` template for this attribute wich drop the column models[modelName].attributes[attribute].delete.others += _.unescape(_.template(tplRelationDown)({
// with the `./builder/columns/dropColumn` template. tableName: modelName,
tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8'); attribute: attribute,
models[modelName].attributes[attribute].delete = _.unescape(_.template(tplRelationDown)({ details: details
}));
} else {
tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn-unique.template'), 'utf8');
models[modelName].attributes[attribute].create.drop += _.unescape(_.template(tplRelationDown)({
tableName: modelName,
attribute: attribute,
details: details
}));
tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'hasOne.template'), 'utf8');
models[modelName].attributes[attribute].delete.drop += _.unescape(_.template(tplRelationUp)({
tableName: modelName, tableName: modelName,
attribute: attribute, attribute: attribute,
details: details details: details
})); }));
} }
} else if (infos.verbose === 'belongsTo') {
else if (infos.verbose === 'belongsTo') { // Force singular foreign key.
// Force singular foreign key
details.attribute = pluralize.singular(details.model); details.attribute = pluralize.singular(details.model);
// Define PK column // Define PK column.
details.column = utilsBookShelf.getPK(modelName, undefined, models); details.column = utilsBookShelf.getPK(modelName, undefined, models);
if (infos.nature === 'oneToMany' || infos.nature === 'oneWay') { if (infos.nature === 'oneToMany' || infos.nature === 'oneWay') {
// Template: create a new column thanks to the attribute's relation. if (!toDrop) {
// Simply make a `create` template for this attribute wich will be added
// to the table template-- either `./builder/tables/selectTable` or
// `./builder/tables/createTableIfNotExists`.
tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsTo.template'), 'utf8'); tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsTo.template'), 'utf8');
models[modelName].attributes[attribute].create = _.unescape(_.template(tplRelationUp)({ rootModels[modelName].attributes[attribute].create.others += _.unescape(_.template(tplRelationUp)({
tableName: modelName, tableName: modelName,
attribute: attribute, attribute: attribute,
details: details, details: details,
nature: infos.nature nature: infos.nature
})); }));
// Template: drop the column.
// Simply make a `delete` template for this attribute wich drop the column
// with the `./builder/columns/dropColumn` template.
tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8'); tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8');
models[modelName].attributes[attribute].delete = _.unescape(_.template(tplRelationDown)({ rootModels[modelName].attributes[attribute].delete.drop += _.unescape(_.template(tplRelationDown)({
tableName: modelName, tableName: modelName,
attribute: attribute, attribute: attribute,
details: details details: details
})); }));
} else { } else {
// Template: create a new column thanks to the attribute's relation. tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropForeign.template'), 'utf8');
// Simply make a `create` template for this attribute wich will be added rootModels[modelName].attributes[attribute].create.drop += _.unescape(_.template(tplRelationDown)({
// to the table template-- either `./builder/tables/selectTable` or tableName: modelName,
// `./builder/tables/createTableIfNotExists`. attribute: attribute,
details: details
}));
tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsTo.template'), 'utf8');
rootModels[modelName].attributes[attribute].delete.others += _.unescape(_.template(tplRelationUp)({
tableName: modelName,
attribute: attribute,
details: details,
nature: infos.nature
}));
}
} else {
if (!toDrop) {
tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsTo-unique.template'), 'utf8'); tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsTo-unique.template'), 'utf8');
models[modelName].attributes[attribute].create = _.unescape(_.template(tplRelationUp)({ rootModels[modelName].attributes[attribute].create.others += _.unescape(_.template(tplRelationUp)({
tableName: modelName, tableName: modelName,
attribute: attribute, attribute: attribute,
details: details details: details
})); }));
// Template: drop the column. tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn-unique.template'), 'utf8');
// Simply make a `delete` template for this attribute wich drop the column rootModels[modelName].attributes[attribute].delete.drop += _.unescape(_.template(tplRelationDown)({
// with the `./builder/columns/dropColumn` template. tableName: modelName,
attribute: attribute,
details: details
}));
} else {
tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8'); tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8');
models[modelName].attributes[attribute].delete = _.unescape(_.template(tplRelationDown)({ rootModels[modelName].attributes[attribute].create.drop += _.unescape(_.template(tplRelationDown)({
tableName: modelName, tableName: modelName,
attribute: attribute, attribute: attribute,
details: details details: details
})); }));
}
}
tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsTo.template'), 'utf8');
rootModels[modelName].attributes[attribute].delete.others += _.unescape(_.template(tplRelationUp)({
tableName: modelName,
attribute: attribute,
details: details,
nature: infos.nature
}));
}
}
} else if (infos.verbose === 'hasMany') {
if (toDrop) {
tplRelationDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropForeign.template'), 'utf8');
rootModels[modelName].attributes[attribute].create.drop += _.unescape(_.template(tplRelationDown)({
tableName: modelName,
attribute: attribute,
details: details
}));
tplRelationUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsTo.template'), 'utf8');
rootModels[modelName].attributes[attribute].delete.others += _.unescape(_.template(tplRelationUp)({
tableName: modelName,
attribute: attribute,
details: details,
nature: infos.nature
}));
}
} else if (infos.verbose === 'belongsToMany') {
// Otherwise if it's a "many-to-many" relationship. // Otherwise if it's a "many-to-many" relationship.
else if (infos.verbose === 'belongsToMany') {
// Save the relationship. // Save the relationship.
const relationship = models[details.collection].attributes[details.via]; const relationship = models[details.collection].attributes[details.via];
// Construct relation table name // Construct relation table name.
const relationTable = _.map(_.sortBy([relationship, details], 'collection'), function (table) { const relationTable = _.map(_.sortBy([relationship, details], 'collection'), function (table) {
return _.snakeCase(pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via)); return _.snakeCase(pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via));
}).join('__'); }).join('__');
// Force singular foreign key // Force singular foreign key.
relationship.attribute = pluralize.singular(relationship.collection); relationship.attribute = pluralize.singular(relationship.collection);
details.attribute = pluralize.singular(details.collection); details.attribute = pluralize.singular(details.collection);
// Define PK column // Define PK column.
details.column = utilsBookShelf.getPK(modelName, undefined, models); details.column = utilsBookShelf.getPK(modelName, undefined, models);
relationship.column = utilsBookShelf.getPK(details.collection, undefined, models); relationship.column = utilsBookShelf.getPK(details.collection, undefined, models);
if (!models.hasOwnProperty(relationTable)) { // Avoid to create table both times.
// Save the relation table as a new model in the scope if (!rootModels.hasOwnProperty(relationTable) || !_.isEmpty(_.get(rootModels, relationTable + '.up.drop'))) {
// aiming to benefit of templates for the table such as // Set objects
// `createTableIfNotExists` and `dropTable`. if (_.isUndefined(_.get(models, relationTable + '.up.others'))) {
models[relationTable] = {}; _.set(rootModels, relationTable + '.up.others', '');
}
// Template: create the table for the `up` export if it doesn't exist. if (_.isUndefined(_.get(rootModels, relationTable + '.up.drop'))) {
// This adds a `up` logic for the relation table. _.set(rootModels, relationTable + '.up.drop', '');
}
if (_.isUndefined(_.get(rootModels, relationTable + '.down.others'))) {
_.set(rootModels, relationTable + '.down.others', '');
}
if (_.isUndefined(_.get(rootModels, relationTable + '.down.drop'))) {
_.set(rootModels, relationTable + '.down.drop', '');
}
if (_.isUndefined(_.get(rootModels, relationTable + '.attributes'))) {
_.set(rootModels, relationTable + '.attributes', {});
}
if (!toDrop) {
// Load templates.
const tplTableUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsToMany.template'), 'utf8'); const tplTableUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsToMany.template'), 'utf8');
models[relationTable].up = _.unescape(_.template(tplTableUp)({ const tplTableDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'dropTable.template'), 'utf8');
// Create relationships table for many-to-many.
rootModels[relationTable].up.others += _.unescape(_.template(tplTableUp)({
models: models, models: models,
tableName: relationTable, tableName: relationTable,
details: details, details: details,
relationship: relationship relationship: relationship
})); }));
// Template: drop the table for the `down` export. if (_.isUndefined(_.get(rootModels, relationTable + '.attributes.fk'))) {
// This adds a `down` logic for the relation table. // Load templates.
const tplFKDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropForeign.template'), 'utf8');
const tplSelectTableDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'down.template'), 'utf8');
// Drop current relationships table on migration rollback.
rootModels[relationTable].down.others += _.unescape(_.template(tplTableDown)({
tableName: relationTable
}));
// Remove foreign key current relationships table before drop the table on migration rollback.
rootModels[relationTable].attributes.fk = {
delete: {
drop: _.unescape(_.template(tplFKDown)({
attribute: details.attribute + '_' + details.column
})) + _.unescape(_.template(tplFKDown)({
attribute: relationship.attribute + '_' + relationship.column
}))
}
};
rootModels[relationTable].down.drop += _.unescape(_.template(tplSelectTableDown)({
models: models,
tableName: relationTable,
attributes: models[relationTable].attributes,
toDrop: true
}));
} else {
const dropMigrationTable = _.unescape(_.template(tplTableDown)({
tableName: relationTable
}));
// Eliminate duplicate
if (rootModels[relationTable].down.drop.indexOf(dropMigrationTable) === -1) {
// Drop current relationships table on migration rollback.
rootModels[relationTable].down.drop += dropMigrationTable;
}
}
} else if (onlyDrop) {
// Load templates.
const tplTableUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'relations', 'belongsToMany.template'), 'utf8');
const tplTableDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'dropTable.template'), 'utf8'); const tplTableDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'dropTable.template'), 'utf8');
models[relationTable].down = _.unescape(_.template(tplTableDown)({ const tplFKDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropForeign.template'), 'utf8');
const dropMigrationTable = _.unescape(_.template(tplTableDown)({
tableName: relationTable
}));
if (_.isUndefined(_.get(rootModels[relationTable].attributes, 'fk.delete')) && _.get(rootModels[modelName].newAttributes[attribute], 'isRemoved') !== true) {
// Eliminate duplicate
if (rootModels[relationTable].up.drop.indexOf(dropMigrationTable) === -1) {
// Drop current relationships table on migration run.
rootModels[relationTable].up.drop += _.unescape(_.template(tplTableDown)({
tableName: relationTable tableName: relationTable
})); }));
} }
// We have to this to be in the up template loop
_.set(rootModels[relationTable], 'newAttributes.fk', {});
_.set(rootModels[relationTable].attributes, 'fk', {
delete: {
drop: ''
}
});
// Drop first FK on migration relation table.
const dropMigrationFK1 = _.unescape(_.template(tplFKDown)({
attribute: details.attribute + '_' + details.column
}));
// Eliminate duplicate
if (rootModels[relationTable].attributes.fk.delete.drop.indexOf(dropMigrationFK1) === -1) {
// Remove foreign key current relationships table before drop the table on migration rollback.
rootModels[relationTable].attributes.fk.delete.drop += dropMigrationFK1;
}
// Drop first FK on migration relation table.
const dropMigrationFK2 = _.unescape(_.template(tplFKDown)({
attribute: relationship.attribute + '_' + relationship.column
}));
// Eliminate duplicate
if (rootModels[relationTable].attributes.fk.delete.drop.indexOf(dropMigrationFK2) === -1) {
rootModels[relationTable].attributes.fk.delete.drop += dropMigrationFK2;
}
// Builder: select the table.
selectTable(rootModels, relationTable);
} else if (_.get(rootModels[modelName].newAttributes[attribute], 'isRemoved') === true) {
// Eliminate duplicate
if (rootModels[relationTable].up.others.indexOf(dropMigrationTable) === -1) {
// Drop current relationships table on migration run.
rootModels[relationTable].up.others += _.unescape(_.template(tplTableDown)({
tableName: relationTable
}));
}
if (_.isUndefined(_.get(rootModels[relationTable].attributes, 'fk.create'))) {
// We have to this to be in the up template loop
_.set(rootModels[relationTable], 'newAttributes.fk.create', {});
_.set(rootModels[relationTable].attributes, 'fk', {
create: {
drop: ''
}
});
// Drop first FK on migration relation table.
const dropMigrationFK1 = _.unescape(_.template(tplFKDown)({
attribute: details.attribute + '_' + details.column
}));
// Eliminate duplicate
if (rootModels[relationTable].attributes.fk.create.drop.indexOf(dropMigrationFK1) === -1) {
// Remove foreign key current relationships table before drop the table on migration rollback.
rootModels[relationTable].attributes.fk.create.drop += dropMigrationFK1;
}
// Drop first FK on migration relation table.
const dropMigrationFK2 = _.unescape(_.template(tplFKDown)({
attribute: relationship.attribute + '_' + relationship.column
}));
// Eliminate duplicate
if (rootModels[relationTable].attributes.fk.create.drop.indexOf(dropMigrationFK2) === -1) {
rootModels[relationTable].attributes.fk.create.drop += dropMigrationFK2;
}
// Builder: select the table.
selectTable(rootModels, relationTable);
}
}
// Eliminate duplicate
if (rootModels[relationTable].down.others.indexOf('createTableIfNotExists(\'' + relationTable + '\'') === -1) {
// Create previous relationships table on migration rollback.
rootModels[relationTable].down.others += _.unescape(_.template(tplTableUp)({
models: models,
tableName: relationTable || relationTable,
details: details,
relationship: relationship
}));
}
}
}
}
} }
}; };

View File

@ -17,21 +17,89 @@ const _ = require('lodash');
module.exports = function (models, modelName) { module.exports = function (models, modelName) {
if (!models[modelName].hasOwnProperty('up')) {
models[modelName].up = {
drop: '',
others: ''
};
}
// Allow to template only when it's necessary
let emptyArrayForDrop = [];
let emptyArrayForOthers = [];
_.forEach(models[modelName].newAttributes, function (attribute, key) {
if (!_.isEmpty(_.get(models[modelName].attributes, key + '.create.drop'))) {
emptyArrayForDrop.push(true);
}
if (!_.isEmpty(_.get(models[modelName].attributes, key + '.create.others'))) {
emptyArrayForOthers.push(true);
}
});
// Template: select the table for the `up` export. // Template: select the table for the `up` export.
// Every attribute with `create` key will be added in this template. // Every attribute with `create` key will be added in this template.
const tplSelectTableUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'up.template'), 'utf8'); const tplSelectTableUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'up.template'), 'utf8');
models[modelName].up = _.unescape(_.template(tplSelectTableUp)({
if (!_.isEmpty(emptyArrayForDrop)) {
models[modelName].up.drop += _.unescape(_.template(tplSelectTableUp)({
models: models, models: models,
tableName: modelName, tableName: modelName,
attributes: models[modelName].newAttributes attributes: models[modelName].newAttributes,
toDrop: true
})); }));
}
if (!_.isEmpty(emptyArrayForOthers)) {
models[modelName].up.others += _.unescape(_.template(tplSelectTableUp)({
models: models,
tableName: modelName,
attributes: models[modelName].newAttributes,
toDrop: false
}));
}
if (!models[modelName].hasOwnProperty('down')) {
models[modelName].down = {
drop: '',
others: ''
};
}
// Allow to template only when it's necessary
emptyArrayForDrop = [];
emptyArrayForOthers = [];
_.forEach(models[modelName].newAttributes, function (attribute, key) {
if (!_.isEmpty(_.get(models[modelName].attributes, key + '.delete.drop'))) {
emptyArrayForDrop.push(true);
}
if (!_.isEmpty(_.get(models[modelName].attributes, key + '.delete.others'))) {
emptyArrayForOthers.push(true);
}
});
// Template: select the table for the `down` export. // Template: select the table for the `down` export.
// Every attribute with `delete` key will be added in this template. // Every attribute with `delete` key will be added in this template.
const tplSelectTableDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'down.template'), 'utf8'); const tplSelectTableDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'down.template'), 'utf8');
models[modelName].down = _.unescape(_.template(tplSelectTableDown)({
if (!_.isEmpty(emptyArrayForDrop)) {
models[modelName].down.drop += _.unescape(_.template(tplSelectTableDown)({
models: models, models: models,
tableName: modelName, tableName: modelName,
attributes: models[modelName].newAttributes attributes: models[modelName].newAttributes,
toDrop: true
})); }));
}
if (!_.isEmpty(emptyArrayForOthers)) {
models[modelName].down.others += _.unescape(_.template(tplSelectTableDown)({
models: models,
tableName: modelName,
attributes: models[modelName].newAttributes,
toDrop: false
}));
}
}; };

View File

@ -15,7 +15,7 @@ const _ = require('lodash');
* Template types * Template types
*/ */
module.exports = function (models, modelName, details, attribute) { module.exports = function (models, modelName, details, attribute, toDrop, onlyDrop) {
// Template: create a new column thanks to the attribute's type. // Template: create a new column thanks to the attribute's type.
// Firt, make sure we know the attribute type. If not, just do it // Firt, make sure we know the attribute type. If not, just do it
@ -26,17 +26,34 @@ module.exports = function (models, modelName, details, attribute) {
} catch (err) { } catch (err) {
tplTypeCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'types', 'specificType.template'), 'utf8'); tplTypeCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'types', 'specificType.template'), 'utf8');
} }
models[modelName].attributes[attribute].create = _.unescape(_.template(tplTypeCreate)({
const tplTypeDelete = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8');
// UP
_.set(models[modelName].attributes, attribute + '.create', {});
if (!_.isUndefined(toDrop) && toDrop) {
// Template: delete a specific column.
models[modelName].attributes[attribute].create.drop = _.unescape(_.template(tplTypeDelete)({
tableName: modelName,
attribute: attribute
}));
}
// Create when it's not an onlyDrop action
if (_.isUndefined(onlyDrop)) {
models[modelName].attributes[attribute].create.others = _.unescape(_.template(tplTypeCreate)({
tableName: modelName, tableName: modelName,
attribute: attribute, attribute: attribute,
details: details details: details
})); }));
}
// Template: make the column chainable with the `defaultTo` template // Template: make the column chainable with the `defaultTo` template
// if a default value is needed. // if a default value is needed.
if (!_.isUndefined(details.defaultTo)) { if (!_.isUndefined(details.defaultTo)) {
const tplDefaultTo = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'defaultTo.template'), 'utf8'); const tplDefaultTo = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'defaultTo.template'), 'utf8');
models[modelName].attributes[attribute].create += _.unescape(_.template(tplDefaultTo)({ models[modelName].attributes[attribute].create.others += _.unescape(_.template(tplDefaultTo)({
details: details details: details
})); }));
} }
@ -45,20 +62,46 @@ module.exports = function (models, modelName, details, attribute) {
// if the column respect uniqueness rule. // if the column respect uniqueness rule.
if (details.unique === true) { if (details.unique === true) {
const tplUnique = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'unique.template'), 'utf8'); const tplUnique = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'unique.template'), 'utf8');
models[modelName].attributes[attribute].create += _.unescape(_.template(tplUnique)({})); models[modelName].attributes[attribute].create.others += _.unescape(_.template(tplUnique)({}));
} }
// Template: make the column chainable with the `primary` template // Template: make the column chainable with the `primary` template
// if the column needs the rule. // if the column needs the rule.
if (details.primary === true) { if (details.primary === true) {
const tplPrimary = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'primary.template'), 'utf8'); const tplPrimary = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'chainables', 'primary.template'), 'utf8');
models[modelName].attributes[attribute].create += _.unescape(_.template(tplPrimary)({})); models[modelName].attributes[attribute].create.others += _.unescape(_.template(tplPrimary)({}));
} }
// DOWN
_.set(models[modelName].attributes, attribute + '.delete', {});
if (!_.isUndefined(toDrop) && toDrop) {
let tplTypeDeleteCreate;
try {
tplTypeDeleteCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'types', models[modelName].oldAttributes[attribute].type + '.template'), 'utf8');
} catch (err) {
tplTypeDeleteCreate = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'types', 'specificType.template'), 'utf8');
}
// Create when it's not an onlyDrop action
if (_.isUndefined(onlyDrop)) {
// Template: delete a specific column. // Template: delete a specific column.
const tplTypeDelete = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'columns', 'dropColumn.template'), 'utf8'); models[modelName].attributes[attribute].delete.drop = _.unescape(_.template(tplTypeDelete)({
models[modelName].attributes[attribute].delete = _.unescape(_.template(tplTypeDelete)({
tableName: modelName, tableName: modelName,
attribute: attribute attribute: attribute
})); }));
}
models[modelName].attributes[attribute].delete.others = _.unescape(_.template(tplTypeDeleteCreate)({
tableName: modelName,
attribute: attribute,
details: models[modelName].oldAttributes[attribute]
}));
} else {
// Template: delete a specific column.
models[modelName].attributes[attribute].delete.others = _.unescape(_.template(tplTypeDelete)({
tableName: modelName,
attribute: attribute
}));
}
}; };

View File

@ -0,0 +1,3 @@
// Delete the `<%= attribute %>` column with unique constraint.
table.dropUnique('<%= tableName.toLowerCase() %>_<%= attribute %>_unique').dropColumn('<%= attribute %>')

View File

@ -0,0 +1,3 @@
// Delete the foreign key on `<%= attribute %>` column.
table.dropForeign('<%= attribute %>').dropColumn('<%= attribute %>')

View File

@ -5,7 +5,7 @@
connection.schema.createTableIfNotExists('<%= tableName %>', function (table) {<% if (_.isObject(options)) { _.forEach(options, function(value, option) { %><% if (models[tableName].options[option] !== false) { %> connection.schema.createTableIfNotExists('<%= tableName %>', function (table) {<% if (_.isObject(options)) { _.forEach(options, function(value, option) { %><% if (models[tableName].options[option] !== false) { %>
<%= models[tableName][option] %>;<% } %><% }); } %> <%= models[tableName][option] %>;<% } %><% }); } %>
<% _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create %>; <% _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create.others %>;
<% }); %> <% }); %>
}).catch(function (err) { }).catch(function (err) {
console.log('Impossible to create the `<%= tableName %>` table.'); console.log('Impossible to create the `<%= tableName %>` table.');

View File

@ -2,11 +2,18 @@
/** /**
* Select the `<%= tableName %>` table. * Select the `<%= tableName %>` table.
*/ */
<% if (toDrop === true) { %>
connection.schema.table('<%= tableName %>', function (table) { connection.schema.table('<%= tableName %>', function (table) {
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].delete %>; <% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].delete.drop %>;
<% }); } %> <% }); } %>
}).catch(function (err) { }).catch(function (err) {
console.log('Impossible to select the `<%= tableName %>` table.'); console.log('Impossible to select the `<%= tableName %>` table.');
console.log(err); console.log(err);
}), }), <% } else { %>
connection.schema.table('<%= tableName %>', function (table) {
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].delete.others %>;
<% }); } %>
}).catch(function (err) {
console.log('Impossible to select the `<%= tableName %>` table.');
console.log(err);
}),<% } %>

View File

@ -2,11 +2,18 @@
/** /**
* Select the `<%= tableName %>` table. * Select the `<%= tableName %>` table.
*/ */
<% if (toDrop === true) { %>
connection.schema.table('<%= tableName %>', function (table) { connection.schema.table('<%= tableName %>', function (table) {
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create %>; <% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create.drop %>;
<% }); } %> <% }); } %>
}).catch(function (err) { }).catch(function (err) {
console.log('Impossible to select the `<%= tableName %>` table.'); console.log('Impossible to select the `<%= tableName %>` table.');
console.log(err); console.log(err);
}), }), <% } else { %>
connection.schema.table('<%= tableName %>', function (table) {
<% if (!_.isEmpty(attributes)) { _.forEach(attributes, function(details, attribute) { %><%= models[tableName].attributes[attribute].create.others %>;
<% }); } %>
}).catch(function (err) {
console.log('Impossible to select the `<%= tableName %>` table.');
console.log(err);
}),<% } %>

View File

@ -8,9 +8,34 @@
*/ */
exports.up = function(connection, Promise) { exports.up = function(connection, Promise) {
<% var dropped = false; var onlyDrop = true;
_.forEach(models, function (definition, model) {
if (!_.isEmpty(_.get(models[model], 'up.drop'))) {
dropped = true;
}
if (!_.isEmpty(_.get(models[model], 'up.others'))) {
onlyDrop = false;
}
});
if (dropped === true && onlyDrop === false) { %> return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'up.drop') %><% }); %>
]).then(function() {
return Promise.all([ return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= models[model].up %><% }); %> <% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'up.others') %><% }); %>
]); ]);
});
<% } else if (dropped === true && onlyDrop === true) { %>
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'up.drop') %><% }); %>
]);
<% } else { %>
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'up.others') %><% }); %>
]);
<% } %>
}; };
/** /**
@ -21,9 +46,35 @@ exports.up = function(connection, Promise) {
*/ */
exports.down = function(connection, Promise) { exports.down = function(connection, Promise) {
<% var dropped = false; var onlyDrop = true;
_.forEach(models, function (definition, model) {
if (!_.isEmpty(_.get(models[model], 'down.drop'))) {
dropped = true;
}
if (!_.isEmpty(_.get(models[model], 'down.others'))) {
onlyDrop = false;
}
});
if (dropped === true && onlyDrop === false) { %>
return Promise.all([ return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= models[model].down %><% }); %> <% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'down.drop') %><% }); %>
]).then(function() {
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'down.others') %><% }); %>
]); ]);
});
<% } else if (dropped === true && onlyDrop === true) { %>
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'down.drop') %><% }); %>
]);
<% } else { %>
return Promise.all([
<% _.forEach(models, function(definition, model) { %><%= _.get(models[model], 'down.others') %><% }); %>
]);
<% } %>
}; };
/** /**

View File

@ -64,6 +64,13 @@ module.exports = function (strapi) {
// Expose the GraphQL schemas at `strapi.schemas` // Expose the GraphQL schemas at `strapi.schemas`
strapi.schemas = schemas; strapi.schemas = schemas;
global.graphql = require('graphql');
global.graphql.query = function * (query, context) {
return this.graphql(schemas, query, {
context: context
});
};
cb(); cb();
}); });
} else { } else {

View File

@ -34,8 +34,6 @@ module.exports = {
*/ */
getObject: function (matchedRoute) { getObject: function (matchedRoute) {
// TODO:
// - Improve way to detect collection/ressource/relationships/related
switch (_.size(matchedRoute.regexp.keys)) { switch (_.size(matchedRoute.regexp.keys)) {
case 0: case 0:
return 'collection'; return 'collection';

View File

@ -69,8 +69,12 @@ module.exports = {
other: '' other: ''
}; };
if (_.isUndefined(models)) {
models = global['strapi'].models;
}
if (association.hasOwnProperty('via') && association.hasOwnProperty('collection')) { if (association.hasOwnProperty('via') && association.hasOwnProperty('collection')) {
const relatedAttribute = strapi.models[association.collection].attributes[association.via]; const relatedAttribute = models[association.collection].attributes[association.via];
types.current = 'collection'; types.current = 'collection';
@ -83,7 +87,7 @@ module.exports = {
types.current = 'modelD'; types.current = 'modelD';
// We have to find if they are a model linked to this key // We have to find if they are a model linked to this key
_.forIn(strapi.models, function (model) { _.forIn(models, function (model) {
_.forIn(model.attributes, function (attribute) { _.forIn(model.attributes, function (attribute) {
if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection')) { if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection')) {
types.other = 'collection'; types.other = 'collection';
@ -102,7 +106,7 @@ module.exports = {
types.current = 'model'; types.current = 'model';
// We have to find if they are a model linked to this key // We have to find if they are a model linked to this key
_.forIn(strapi.models, function (model) { _.forIn(models, function (model) {
_.forIn(model.attributes, function (attribute) { _.forIn(model.attributes, function (attribute) {
if (attribute.hasOwnProperty('via') && attribute.via === key) { if (attribute.hasOwnProperty('via') && attribute.via === key) {
if (attribute.hasOwnProperty('collection')) { if (attribute.hasOwnProperty('collection')) {

View File

@ -198,5 +198,14 @@ Then, you can apply one or more policies on each query and mutation.
} }
``` ```
## Use GraphQL in your codebase
You can make GraphQL query in your codebase. The `graphql` is exposed in global, and Strapi add a new function called `query` to easily make GraphQL query.
```javascript
this.body = yield graphql.query("{articles{title}}", this);
// Don't forget to send the context. This is needed to apply permissions.
```
!!! note !!! note
The policy doesn't need to be in the same API folder. The GraphQL permissions are based on the global `strapi.policies` variable which is an aggregate of the policies of the whole application. Also, the request is apply to the policies, in others words, this means you can handle sessions and cookies in the policy as usual. The policy doesn't need to be in the same API folder. The GraphQL permissions are based on the global `strapi.policies` variable which is an aggregate of the policies of the whole application. Also, the request is apply to the policies, in others words, this means you can handle sessions and cookies in the policy as usual.