Skip to content

Commit 3deec4f

Browse files
committed
Finishing persist method
1 parent 788a742 commit 3deec4f

File tree

10 files changed

+481
-201
lines changed

10 files changed

+481
-201
lines changed

dist/vuex-orm-apollo.esm.js

Lines changed: 150 additions & 34 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"@types/graphql": "^0.12.3",
4444
"@types/inflection": "^1.5.28",
4545
"@types/lodash-es": "^4.17.0",
46-
"@vuex-orm/core": "^0.17.0",
46+
"@vuex-orm/core": "^0.19.0",
4747
"apollo-cache-inmemory": "^1.1.7",
4848
"apollo-client": "^2.2.2",
4949
"apollo-link": "^1.0.7",

src/interfaces.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import Model from './model';
22

3+
export type DispatchFunction = (action: string, data: Data) => Promise<any>;
4+
35
export interface ActionParams {
46
commit: any;
5-
dispatch: any;
7+
dispatch: DispatchFunction;
68
getters: any;
79
rootGetters: any;
810
rootState: any;
911
state: any;
1012
filter?: Filter;
13+
id?: number;
1114
data?: Data;
1215
}
1316

src/model.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export default class Model {
3030
const fields: Array<string> = [];
3131

3232
this.fields.forEach((field: Field, name: string) => {
33-
// field.constructor.name is one of Attr, BelongsToMany, BelongsTo, HasMany, HasManyBy, HasOne
33+
// field.constructor.name is one of Increment, Attr, BelongsToMany, BelongsTo, HasMany, HasManyBy, HasOne
3434
// TODO import the classes from Vuex-ORM and use instanceof instead
35-
if (field.constructor.name === 'Attr' && !name.endsWith('Id')) {
35+
if (this.fieldIsAttribute(field) && !name.endsWith('Id')) {
3636
fields.push(name);
3737
}
3838
});
@@ -47,11 +47,15 @@ export default class Model {
4747
const relations = new Map<string, Field>();
4848

4949
this.fields.forEach((field: Field, name: string) => {
50-
if (field.constructor.name !== 'Attr') {
50+
if (!this.fieldIsAttribute(field)) {
5151
relations.set(name, field);
5252
}
5353
});
5454

5555
return relations;
5656
}
57+
58+
private fieldIsAttribute (field: Field): boolean {
59+
return field.constructor.name === 'Attr' || field.constructor.name === 'Increment';
60+
}
5761
}

src/queryBuilder.ts

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Arguments, Data, Field } from './interfaces';
44
import Model from './model';
55
import gql from 'graphql-tag';
66
import Logger from './logger';
7-
import { capitalizeFirstLetter } from './utils';
7+
import { downcaseFirstLetter, upcaseFirstLetter } from './utils';
88
const inflection = require('inflection');
99

1010
/**
@@ -60,8 +60,9 @@ export default class QueryBuilder {
6060
* @returns {String}
6161
*/
6262
public buildArguments (args: Arguments | undefined,
63-
signature: boolean = false,
64-
valuesAsVariables: boolean = false): string {
63+
signature: boolean = false,
64+
valuesAsVariables: boolean = false,
65+
allowIdFields: boolean = false): string {
6566
let returnValue: string = '';
6667
let first: boolean = true;
6768

@@ -70,13 +71,16 @@ export default class QueryBuilder {
7071
let value: any = args[key];
7172

7273
// Ignore ids and connections
73-
if (!(value instanceof Array || key === 'id')) {
74+
if (!(value instanceof Array || (key === 'id' && !allowIdFields))) {
7475
let typeOrValue: any = '';
7576

7677
if (signature) {
7778
if (typeof value === 'object' && value.__type) {
7879
// Case 2 (User!)
7980
typeOrValue = value.__type + 'Input!';
81+
} else if (key === 'id') {
82+
// Case 1 (ID!)
83+
typeOrValue = 'ID!';
8084
} else {
8185
// Case 1 (String!)
8286
typeOrValue = typeof value === 'number' ? 'Number!' : 'String!';
@@ -137,7 +141,7 @@ export default class QueryBuilder {
137141
* @param {boolean} recursiveCall
138142
* @returns {Data}
139143
*/
140-
public transformIncomingData (data: Data | Array<Data>, recursiveCall: boolean = false): Data {
144+
public transformIncomingData (data: Data | Array<Data>, mutationResult: boolean = false, recursiveCall: boolean = false): Data {
141145
let result: Data = {};
142146

143147
if (!recursiveCall) {
@@ -146,15 +150,22 @@ export default class QueryBuilder {
146150
}
147151

148152
if (data instanceof Array) {
149-
result = data.map(d => this.transformIncomingData(d, true));
153+
result = data.map(d => this.transformIncomingData(d, mutationResult, true));
150154
} else {
151155
Object.keys(data).forEach((key) => {
152156
if (data[key]) {
153157
if (data[key] instanceof Object) {
154158
if (data[key].nodes) {
155-
result[inflection.pluralize(key)] = this.transformIncomingData(data[key].nodes, true);
159+
result[inflection.pluralize(key)] = this.transformIncomingData(data[key].nodes, mutationResult, true);
156160
} else {
157-
result[inflection.singularize(key)] = this.transformIncomingData(data[key], true);
161+
let newKey = key;
162+
163+
if (mutationResult) {
164+
newKey = newKey.replace(/^(create|update)(.+)/, '$2');
165+
newKey = downcaseFirstLetter(newKey);
166+
}
167+
168+
result[inflection.singularize(newKey)] = this.transformIncomingData(data[key], mutationResult, true);
158169
}
159170
} else if (key === 'id') {
160171
result[key] = parseInt(data[key], 0);
@@ -183,7 +194,7 @@ export default class QueryBuilder {
183194
const relationQueries: Array<string> = [];
184195

185196
model.getRelations().forEach((field: Field, name: string) => {
186-
if (!rootModel || name !== rootModel.singularName && name !== rootModel.pluralName) {
197+
if (!rootModel || (name !== rootModel.singularName && name !== rootModel.pluralName)) {
187198
const multiple: boolean = field.constructor.name !== 'BelongsTo';
188199
relationQueries.push(this.buildField(name, multiple, undefined, false, rootModel || model));
189200
}
@@ -202,10 +213,16 @@ export default class QueryBuilder {
202213
* @param {string} name
203214
* @returns {string}
204215
*/
205-
public buildField (model: Model | string, multiple: boolean = true, args?: Arguments, withVars: boolean = false, rootModel?: Model, name?: string): string {
216+
public buildField (model: Model | string,
217+
multiple: boolean = true,
218+
args?: Arguments,
219+
withVars: boolean = false,
220+
rootModel?: Model,
221+
name?: string,
222+
allowIdFields: boolean = false): string {
206223
model = this.getModel(model);
207224

208-
let params: string = this.buildArguments(args, false, withVars);
225+
let params: string = this.buildArguments(args, false, withVars, allowIdFields);
209226

210227
const fields = `
211228
${model.getQueryFields().join(' ')}
@@ -251,21 +268,20 @@ export default class QueryBuilder {
251268
* @param {Model} model
252269
* @param {string}prefix
253270
* @returns {any}
271+
*
272+
* TODO: Refactor to avoid prefix param
254273
*/
255-
public buildMutation (model: Model, prefix: string = 'create') {
256-
const name = `${prefix}${capitalizeFirstLetter(model.singularName)}`;
257-
258-
// Send the request to the GraphQL API
259-
const signature = this.buildArguments({ contract: { __type: 'Contract' } }, true);
260-
261-
const field = this.buildField(
262-
model,
263-
false,
264-
{ contract: { __type: 'Contract' } },
265-
true,
266-
undefined,
267-
name
268-
);
274+
public buildMutation (model: Model, id?: number, prefix: string = 'create') {
275+
const name: string = `${prefix}${upcaseFirstLetter(model.singularName)}`;
276+
let args: Data = { [model.singularName]: { __type: upcaseFirstLetter(model.singularName) } };
277+
278+
if (prefix === 'delete') {
279+
if (!id) throw new Error('No ID given.');
280+
args = { id };
281+
}
282+
283+
const signature: string = this.buildArguments(args, true, false, true);
284+
const field = this.buildField(model, false, args, true, model, name, true);
269285

270286
const query = `
271287
mutation ${name}${signature} {

src/utils.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
* @param {string} input
55
* @returns {string}
66
*/
7-
export function capitalizeFirstLetter (input: string) {
7+
export function upcaseFirstLetter (input: string) {
88
return input.charAt(0).toUpperCase() + input.slice(1);
99
}
10+
11+
/**
12+
* Down cases the first letter of the given string.
13+
*
14+
* @param {string} input
15+
* @returns {string}
16+
*/
17+
export function downcaseFirstLetter (input: string) {
18+
return input.charAt(0).toLowerCase() + input.slice(1);
19+
}

src/vuex-orm-apollo.ts

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ import Model from './model';
22
import { ApolloClient } from 'apollo-client';
33
import { HttpLink } from 'apollo-link-http';
44
import { InMemoryCache } from 'apollo-cache-inmemory';
5-
import gql from 'graphql-tag';
6-
import { Data, ActionParams, Arguments, ORMModel } from './interfaces';
5+
import { Data, ActionParams, Arguments, ORMModel, DispatchFunction } from './interfaces';
76
import Logger from './logger';
87
import QueryBuilder from './queryBuilder';
9-
import { capitalizeFirstLetter } from './utils';
108

119
const inflection = require('inflection');
1210

@@ -91,7 +89,7 @@ export default class VuexORMApollo {
9189

9290
this.components.subActions.persist = this.persist.bind(this);
9391
this.components.subActions.push = this.push.bind(this);
94-
// this.components.subActions.destroy = this.destroy.bind(this);
92+
this.components.subActions.destroy = this.destroy.bind(this);
9593
// this.components.subActions.destroyAll = this.destroyAll.bind(this);
9694
}
9795

@@ -109,7 +107,7 @@ export default class VuexORMApollo {
109107
const data = await this.apolloRequest(query);
110108

111109
// Insert incoming data into the store
112-
this.storeData(data, dispatch);
110+
await this.insertData(data, dispatch);
113111
}
114112

115113
/**
@@ -120,8 +118,14 @@ export default class VuexORMApollo {
120118
* @param {any} id
121119
* @returns {Promise<void>}
122120
*/
123-
private async persist ({ state, dispatch }: ActionParams, { data }: ActionParams) {
124-
return this.mutate('create', data, dispatch, this.getModel(state.$name));
121+
private async persist ({ state, dispatch }: ActionParams, { id }: ActionParams) {
122+
const model = this.getModel(state.$name);
123+
const data = model.baseModel.getters('find')(id);
124+
125+
await this.mutate('create', data, dispatch, this.getModel(state.$name));
126+
127+
// TODO is this really necessary?
128+
return model.baseModel.getters('find')(id);
125129
}
126130

127131
/**
@@ -135,6 +139,28 @@ export default class VuexORMApollo {
135139
return this.mutate('update', data, dispatch, this.getModel(state.$name));
136140
}
137141

142+
/**
143+
* Will be called, when dispatch('entities/something/destroy') is called.
144+
*
145+
* @param {any} state
146+
* @param {any} dispatch
147+
* @param {Data} id
148+
* @returns {Promise<void>}
149+
*/
150+
private async destroy ({ state, dispatch }: ActionParams, { id }: ActionParams): Promise<void> {
151+
const model = this.getModel(state.$name);
152+
153+
if (id) {
154+
const query = this.queryBuilder.buildMutation(model, id, 'delete');
155+
156+
// Send GraphQL Mutation
157+
await this.apolloClient.mutate({
158+
mutation: query,
159+
variables: { id }
160+
});
161+
}
162+
}
163+
138164
/**
139165
* Contains the logic to save (persist or push) data.
140166
*
@@ -144,30 +170,27 @@ export default class VuexORMApollo {
144170
* @param {Model} model
145171
* @returns {Promise<any>}
146172
*/
147-
private async mutate (action: string, data: Data | undefined, dispatch: Function, model: Model) {
173+
private async mutate (action: string, data: Data | undefined, dispatch: DispatchFunction, model: Model): Promise<any> {
148174
if (data) {
149-
const query = this.queryBuilder.buildMutation(model, action);
175+
const id = action === 'create' ? undefined : data.id;
176+
const query = this.queryBuilder.buildMutation(model, id, action);
177+
178+
const variables: Data = {
179+
[model.singularName]: this.queryBuilder.transformOutgoingData(data)
180+
};
181+
182+
if (id) variables['id'] = id;
150183

151184
// Send GraphQL Mutation
152185
const response = await this.apolloClient.mutate({
153-
'mutation': query,
154-
'variables': {
155-
[model.singularName]: this.queryBuilder.transformOutgoingData(data)
156-
}
186+
mutation: query,
187+
variables
157188
});
158189

159-
// Remove the original data
160-
// FIXME how? https://github.com/vuex-orm/vuex-orm/issues/78
161-
// model.baseModel.dispatch('delete', { id });
162-
163190
// Insert incoming data into the store
164-
const newData = this.queryBuilder.transformIncomingData(response.data as Data);
165-
this.storeData(newData, dispatch);
166-
167-
return newData;
191+
const newData = this.queryBuilder.transformIncomingData(response.data as Data, true);
192+
return this.updateData(newData, dispatch, data.id);
168193
}
169-
170-
return {};
171194
}
172195

173196
/**
@@ -183,14 +206,33 @@ export default class VuexORMApollo {
183206
}
184207

185208
/**
186-
* Saves incoming data into the store.
209+
* Inserts incoming data into the store.
187210
*
188211
* @param {Data} data
189212
* @param {Function} dispatch
213+
* @param {boolean} update
190214
*/
191-
private storeData (data: Data, dispatch: Function) {
192-
Object.keys(data).forEach((key) => {
193-
dispatch('insert', { data: data[key] });
215+
private async insertData (data: Data, dispatch: DispatchFunction) {
216+
Object.keys(data).forEach(async (key) => {
217+
await dispatch('insertOrUpdate', { data: data[key] });
194218
});
195219
}
220+
221+
/**
222+
* Updates an existing record in the store with new data. This method can only update one single record, so
223+
* it takes the first record of the first field from the data object!
224+
* @param {Data} data
225+
* @param {Function} dispatch
226+
* @param id
227+
*/
228+
private async updateData (data: Data, dispatch: DispatchFunction, id: number | string) {
229+
// We only take the first field!
230+
data = data[Object.keys(data)[0]];
231+
232+
if (data instanceof Array) {
233+
data = data[0];
234+
}
235+
236+
return dispatch('update', { where: id, data });
237+
}
196238
}

0 commit comments

Comments
 (0)