Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutations working correctly now with 'AndFetchById' methods #34

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ examples/*/*.db
examples/websql/objection.js
*.iml
*.DS_Store
*.~js
223 changes: 197 additions & 26 deletions lib/SchemaBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ var _ = require('lodash')
, defaultArgFactories = require('./argFactories')
, GraphQLObjectType = graphqlRoot.GraphQLObjectType
, GraphQLSchema = graphqlRoot.GraphQLSchema
, GraphQLList = graphqlRoot.GraphQLList;
, GraphQLList = graphqlRoot.GraphQLList
, pluralize = require('pluralize');

// Default arguments that are excluded from the relation arguments.
const OMIT_FROM_RELATION_ARGS = [
Expand All @@ -18,6 +19,10 @@ const OMIT_FROM_RELATION_ARGS = [
"range"
];

const GRAPHQL_META_FIELDS = [
"__typename"
];

// GraphQL AST node types.
const KIND_FRAGMENT_SPREAD = 'FragmentSpread';
const KIND_VARIABLE = 'Variable';
Expand Down Expand Up @@ -100,10 +105,27 @@ class SchemaBuilder {
_.forOwn(this.models, (modelData) => {
const defaultFieldName = fieldNameForModel(modelData.modelClass);
const singleFieldName = modelData.opt.fieldName || defaultFieldName;
const listFieldName = modelData.opt.listFieldName || (defaultFieldName + 's');
const listFieldName = modelData.opt.listFieldName || pluralize(defaultFieldName);

fields[singleFieldName] = this._rootSingleField(modelData);
fields[listFieldName] = this._rootListField(modelData);
fields[singleFieldName] = this._rootQuerySingleField(modelData);
fields[listFieldName] = this._rootQueryListField(modelData);
});
return fields;
}
}),
mutation: newGraphQLObjectType({
name: 'Mutation',
fields: () => {
const fields = {};
_.forOwn(this.models, (modelData) => {
const defaultFieldName = fieldNameForModel(modelData.modelClass);
const singleFieldName = modelData.opt.fieldName || defaultFieldName;
const listFieldName = modelData.opt.listFieldName || pluralize(defaultFieldName);
fields['create' + _.upperFirst(singleFieldName)] = this._rootMutationSingleField(modelData, 'create');
fields['update' + _.upperFirst(singleFieldName)] = this._rootMutationSingleField(modelData, 'update');
fields['patch' + _.upperFirst(singleFieldName)] = this._rootMutationSingleField(modelData, 'patch');
fields['delete' + _.upperFirst(singleFieldName)] = this._rootMutationSingleField(modelData, 'delete');
// fields[listFieldName] = this._rootMutationListField(modelData);
});

return fields;
Expand All @@ -120,21 +142,28 @@ class SchemaBuilder {
}, {});
}

_rootSingleField(modelData) {
_rootQuerySingleField(modelData) {
return {
type: this._typeForModel(modelData),
args: modelData.args,
resolve: this._resolverForModel(modelData, (query) => {
resolve: this._resolverForQuery(modelData, (query) => {
query.first();
})
};
}

_rootListField(modelData) {
_rootQueryListField(modelData) {
return {
type: new GraphQLList(this._typeForModel(modelData)),
args: modelData.args,
resolve: this._resolverForModel(modelData)
resolve: this._resolverForQuery(modelData)
};
}
_rootMutationSingleField(modelData, mode) {
return {
type: this._typeForModel(modelData),
args: modelData.args,
resolve: this._resolverForMutation(modelData, mode)
};
}

Expand Down Expand Up @@ -184,8 +213,8 @@ class SchemaBuilder {
}

_relationField(modelData, relation) {
if (relation instanceof objection.HasOneRelation
|| relation instanceof objection.BelongsToOneRelation
if (relation instanceof objection.HasOneRelation
|| relation instanceof objection.BelongsToOneRelation
|| relation instanceof objection.HasOneThroughRelation) {
return {
type: this._typeForModel(modelData),
Expand All @@ -201,7 +230,7 @@ class SchemaBuilder {
}
}

_resolverForModel(modelData, extraQuery) {
_resolverForQuery(modelData, extraQuery) {
return (ctx, ignore1, ignore2, data) => {
ctx = ctx || {};

Expand Down Expand Up @@ -235,6 +264,31 @@ class SchemaBuilder {
return builder.then(toJson);
};
}
// Mutations
_resolverForMutation(modelData, mode) {
return (ctx, ignore1, ignore2, data) => {
ctx = ctx || {};
const modelClass = modelData.modelClass;
const ast = (data.fieldASTs || data.fieldNodes)[0];
const eager = this._buildEager(ast, modelClass, data);
const argFilter = this._filterForMutationArgs(ast, modelClass, data.variableValues, mode);

const builder = modelClass.query(ctx.knex);

if (ctx.onQuery) {
ctx.onQuery(builder, ctx);
}
if (argFilter) {
builder.modify(argFilter);
}

if (eager.expression) {
builder.eager(eager.expression, eager.filters);
}

return builder.then(toJson);
};
}

_buildEager(astNode, modelClass, astRoot) {
const eagerExpr = this._buildEagerSegment(astNode, modelClass, astRoot);
Expand Down Expand Up @@ -327,7 +381,7 @@ class SchemaBuilder {
return expression + fragmentExprString;
}

_filterForArgs(astNode, modelClass, variableValues) {
_filterForArgs(astNode, modelClass, variables) {
const args = astNode.arguments;

if (args.length === 0) {
Expand All @@ -339,15 +393,8 @@ class SchemaBuilder {

for (let i = 0, l = args.length; i < l; ++i) {
const arg = args[i];
let value;

if (arg.value.kind === KIND_VARIABLE) {
value = variableValues[arg.value.name.value];
} else if (_.has(arg.value, 'value')) {
value = arg.value.value;
} else {
value = _.map(arg.value.values, 'value')
}
const value = this._argValue(arg.value, variables);

argObjects[i] = {
name: arg.name.value,
Expand All @@ -363,14 +410,89 @@ class SchemaBuilder {
};
}

_filterForMutationArgs(astNode, modelClass, variables, recordMode) {
const args = astNode.arguments;

if (args.length === 0) {
return null;
}

const modelData = this.models[modelClass.tableName];
const argObjects = [];
var argFilters = [];
var idColumnValue = 0;
const jsonSchema = modelClass.jsonSchema;

for (let i = 0, l = args.length; i < l; ++i) {
const arg = args[i];
const value = this._argValue(arg.value, variables);

if (arg.name.value === 'idEq') {
idColumnValue = value
}
if (jsonSchema.properties[arg.name.value]) {
argObjects.push([ // Save as array object for Mutation
arg.name.value,
value
]);
} else { // Save as Object for Query
argFilters.push({
name: arg.name.value,
value
});
}
}

var argUpdates = argObjects.reduce(function(pv, cv) {
pv[cv[0]] = cv[1];
return pv;
}, {});

return (builder) => {
switch (recordMode) {
case 'create':
builder.insertAndFetch(argUpdates);
break;
case 'update':
builder.updateAndFetchById(idColumnValue, argUpdates);
break;
case 'patch':
builder.patchAndFetchById(idColumnValue, argUpdates);
break
case 'delete':
builder.delete();
break;
default:
throw new Error(`objection-graphql cannot handle mutation mode ${mode}`);
}

for (let i = 0, l = argFilters.length; i < l; ++i) {
const argFilter = argFilters[i];
modelData.args[argFilter.name].query(builder, argFilter.value);
}
};
}

_argValue(value, variables) {
if (value.kind === KIND_VARIABLE) {
return variables[value.name.value];
} else if ('value' in value) {
return value.value;
} else if (Array.isArray(value.values)) {
return value.values.map(value => this._argValue(value, variables));
} else {
throw new Error(`objection-graphql cannot handle argument value ${JSON.stringify(value)}`);
}
}

_filterForSelects(astNode, modelClass, astRoot) {
if (!this.enableSelectFiltering) {
return null;
}

const relations = modelClass.getRelations();
const selects = this._collectSelects(astNode, relations, astRoot.fragments, []);
const selects = this._collectSelects(astNode, relations, astRoot.fragments, []);

if (selects.length === 0) {
return null;
}
Expand All @@ -390,6 +512,36 @@ class SchemaBuilder {
};
}

_filterForMutations(astNode, modelClass, astRoot) {
// if (!this.enableSelectFiltering) {
// return null;
// }

const relations = modelClass.getRelations();
const updates = this._collectUpdates(astNode, relations, astRoot.fragments, []);

const variables = astRoot.variableValues;
const args = astNode.arguments;
if (updates.length === 0) {
return null;
}

return (builder) => {
const jsonSchema = modelClass.jsonSchema;

builder.select(updates.map(it => {
const col = modelClass.propertyNameToColumnName(it);

const modelDataArg = astRoot.variableValues;
if (jsonSchema.properties[it]) {
return `${builder.tableRefFor(modelClass)}.${col}`;
} else {
return col;
}
}));
};
}

_collectSelects(astNode, relations, fragments, selects) {
for (let i = 0, l = astNode.selectionSet.selections.length; i < l; ++i) {
const selectionNode = astNode.selectionSet.selections[i];
Expand All @@ -398,28 +550,47 @@ class SchemaBuilder {
this._collectSelects(fragments[selectionNode.name.value], relations, fragments, selects);
} else {
const relation = relations[selectionNode.name.value];
const isMetaField = GRAPHQL_META_FIELDS.indexOf(selectionNode.name.value) !== -1;

if (!relation) {
if (!relation && !isMetaField) {
selects.push(selectionNode.name.value);
}
}
}

return selects;
}
_collectUpdates(astNode, relations, fragments, updates) {
for (let i = 0, l = astNode.selectionSet.selections.length; i < l; ++i) {
const selectionNode = astNode.selectionSet.selections[i];
if (selectionNode.kind === KIND_FRAGMENT_SPREAD) {
this._collectUpdates(fragments[selectionNode.name.value], relations, fragments, updates);
} else {
const relation = relations[selectionNode.name.value];
const isMetaField = GRAPHQL_META_FIELDS.indexOf(selectionNode.name.value) !== -1;
if (!relation && !isMetaField) {
updates.push(selectionNode.name.value);
}
}
}
return updates;
}
}

function fieldNameForModel(modelClass) {
return _.camelCase(utils.typeNameForModel(modelClass));
}

function toJson(result) {
console.warn('JSON result: ', JSON.stringify(result, null, 2));
if (_.isArray(result)) {
for (var i = 0, l = result.length; i < l; ++i) {
for (let i = 0, l = result.length; i < l; ++i) {
result[i] = result[i].$toJson();
}
} else if (_.isNumber(+result)) {
// return as is - number of Rows affected during Mutations
} else {
result = result.$toJson();
result = result && result.$toJson();
}

return result;
Expand Down
Loading