Skip to content

Commit 128a844

Browse files
committed
feat: add support for alias mongoose option. It allows to have different names in schema and database. See https://mongoosejs.com/docs/guide.html#aliases
Closes #194
1 parent f7394ad commit 128a844

33 files changed

+264
-66
lines changed

README.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { composeWithMongoose } from 'graphql-compose-mongoose';
3434
// For using node 8 and above (native async/await)
3535
import { composeWithMongoose } from 'graphql-compose-mongoose/node8';
3636

37-
// Source code without Flowtype declarations
37+
// Source code without FlowType declarations
3838
import { composeWithMongoose } from 'graphql-compose-mongoose/es';
3939
```
4040

@@ -56,7 +56,7 @@ Small explanation for variables naming:
5656

5757
- `UserSchema` - this is a mongoose schema
5858
- `User` - this is a mongoose model
59-
- `UserTC` - this is a `ObjectTypeComposer` instance for User. `ObjectTypeComposer` has `GraphQLObjectType` inside, avaliable via method `UserTC.getType()`.
59+
- `UserTC` - this is a `ObjectTypeComposer` instance for User. `ObjectTypeComposer` has `GraphQLObjectType` inside, available via method `UserTC.getType()`.
6060
- Here and in all other places of code variables suffix `...TC` means that this is `ObjectTypeComposer` instance, `...ITC` - `InputTypeComposer`, `...ETC` - `EnumTypeComposer`.
6161

6262
```js
@@ -79,17 +79,18 @@ const UserSchema = new mongoose.Schema({
7979
type: Number,
8080
index: true,
8181
},
82-
languages: {
82+
ln: {
8383
type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
8484
default: [],
85+
alias: 'languages', // in schema `ln` will be named as `languages`
8586
},
8687
contacts: { // another mongoose way for providing embedded documents
8788
email: String,
8889
phones: [String], // array of strings
8990
},
9091
gender: { // enum field with values
9192
type: String,
92-
enum: ['male', 'female', 'ladyboy'],
93+
enum: ['male', 'female'],
9394
},
9495
someMixed: {
9596
type: mongoose.Schema.Types.Mixed,
@@ -201,7 +202,7 @@ Variable Namings
201202
const CharacterDTC = composeWithMongooseDiscriminators(CharacterModel, baseOptions);
202203

203204
// create Discriminator Types
204-
const droidTypeConverterOptions = { // this options will be merged with baseOptions -> customisationsOptions
205+
const droidTypeConverterOptions = { // this options will be merged with baseOptions -> customizationsOptions
205206
fields: {
206207
remove: ['makeDate'],
207208
}
@@ -528,6 +529,19 @@ const UsersSchema = new Schema({
528529
});
529530
```
530531

532+
### Can field name in schema have different name in database?
533+
534+
Yes, it can. This package understands mongoose [`alias` option](https://mongoosejs.com/docs/guide.html#aliases) for fields. Just provide `alias: 'country'` for field `c` and you get `country` field name in GraphQL schema and Mongoose model but `c` field in database:
535+
536+
```js
537+
const childSchema = new Schema({
538+
c: {
539+
type: String,
540+
alias: 'country'
541+
}
542+
});
543+
```
544+
531545
## Customization options
532546

533547
When we convert model `const UserTC = composeWithMongoose(User, customizationOptions);` you may tune every piece of future derived types and resolvers.

src/__mocks__/userModel.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ const UserSchema: SchemaType<any> = new Schema(
2626
ref: 'UserModel',
2727
},
2828

29-
name: {
29+
n: {
3030
type: String,
3131
required: true,
3232
description: 'Person name',
33+
alias: 'name',
3334
},
3435

3536
age: {

src/__tests__/__snapshots__/integration-test.js.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Array [
88
"employment",
99
"contacts",
1010
"_id",
11-
"name",
11+
"n",
1212
"age",
1313
"gender",
1414
"relocation",
@@ -17,6 +17,7 @@ Array [
1717
"createdAt",
1818
"updatedAt",
1919
"__v",
20+
"name",
2021
"nameVirtual",
2122
"id",
2223
]

src/__tests__/fieldConverter-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('fieldConverter', () => {
2626

2727
describe('getFieldsFromModel()', () => {
2828
it('should get fieldsMap from mongoose model', () => {
29-
expect(Object.keys(fields)).toEqual(expect.arrayContaining(['name', 'createdAt', '_id']));
29+
expect(Object.keys(fields)).toEqual(expect.arrayContaining(['n', 'createdAt', '_id']));
3030
});
3131

3232
it('should skip double undescored fields', () => {
@@ -101,7 +101,7 @@ describe('fieldConverter', () => {
101101
});
102102

103103
it('should derive SCALAR', () => {
104-
expect(deriveComplexType(fields.name)).toBe(ComplexTypes.SCALAR);
104+
expect(deriveComplexType(fields.n)).toBe(ComplexTypes.SCALAR);
105105
expect(deriveComplexType(fields.relocation)).toBe(ComplexTypes.SCALAR);
106106
expect(deriveComplexType(fields.age)).toBe(ComplexTypes.SCALAR);
107107
expect(deriveComplexType(fields.createdAt)).toBe(ComplexTypes.SCALAR);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* @flow */
2+
/* eslint-disable no-await-in-loop */
3+
4+
import mongoose from 'mongoose';
5+
import MongodbMemoryServer from 'mongodb-memory-server';
6+
import { schemaComposer, graphql } from 'graphql-compose';
7+
import { composeWithMongoose } from '../../index';
8+
9+
let mongoServer;
10+
beforeAll(async () => {
11+
mongoServer = new MongodbMemoryServer();
12+
const mongoUri = await mongoServer.getConnectionString();
13+
await mongoose.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true });
14+
// mongoose.set('debug', true);
15+
});
16+
17+
afterAll(() => {
18+
mongoose.disconnect();
19+
mongoServer.stop();
20+
});
21+
22+
// May require additional time for downloading MongoDB binaries
23+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
24+
25+
describe('issue #194 - useAlias', () => {
26+
const UserSchema = new mongoose.Schema({
27+
e: {
28+
type: String,
29+
alias: 'emailAddress',
30+
},
31+
});
32+
33+
const User = mongoose.model('User', UserSchema);
34+
const UserTC = composeWithMongoose(User);
35+
36+
it('check that virtual field works', async () => {
37+
// INIT GRAPHQL SCHEMA
38+
schemaComposer.Query.addFields({ findMany: UserTC.getResolver('findMany') });
39+
schemaComposer.Mutation.addFields({
40+
createOne: UserTC.getResolver('createOne'),
41+
});
42+
const schema = schemaComposer.buildSchema();
43+
44+
const res = await graphql.graphql({
45+
schema,
46+
source:
47+
'mutation { createOne(record: { emailAddress: "[email protected]" }) { record { emailAddress } } }',
48+
});
49+
expect(res).toEqual({ data: { createOne: { record: { emailAddress: '[email protected]' } } } });
50+
51+
const res2 = await graphql.graphql({
52+
schema,
53+
source: 'query { findMany { emailAddress } }',
54+
});
55+
expect(res2).toEqual({ data: { findMany: [{ emailAddress: '[email protected]' }] } });
56+
});
57+
});

src/fieldsConverter.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,13 @@ export function convertModelToGraphQL<TSource, TContext>(
142142
const graphqlFields = {};
143143
const requiredFields = [];
144144

145-
Object.keys(mongooseFields).forEach((fieldName) => {
146-
const mongooseField: MongooseFieldT = mongooseFields[fieldName];
145+
Object.keys(mongooseFields).forEach((key) => {
146+
const mongooseField: MongooseFieldT = mongooseFields[key];
147+
148+
let fieldName = key;
149+
if (typeof (mongooseField: any)?.options?.alias === 'string') {
150+
fieldName = (mongooseField: any)?.options?.alias;
151+
}
147152

148153
if (
149154
(mongooseField: any).isRequired &&

src/resolvers/__tests__/createMany-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ describe('createMany() ->', () => {
111111

112112
const docs = await UserModel.collection.find({ _id: { $in: res.recordIds } }).toArray();
113113
expect(docs.length).toBe(2);
114-
expect(docs[0].name).toBe(checkedName);
115-
expect(docs[1].name).toBe(checkedName);
114+
expect(docs[0].n).toBe(checkedName);
115+
expect(docs[1].n).toBe(checkedName);
116116
});
117117

118118
it('should return payload.records', async () => {

src/resolvers/__tests__/createOne-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('createOne() ->', () => {
7777
});
7878

7979
const doc = await UserModel.collection.findOne({ _id: res.record._id });
80-
expect(doc.name).toBe(checkedName);
80+
expect(doc.n).toBe(checkedName);
8181
});
8282

8383
it('should return payload.record', async () => {

src/resolvers/__tests__/removeById-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ describe('removeById() ->', () => {
145145
});
146146
await expect(result).rejects.toMatchSnapshot();
147147
const exist = await UserModel.collection.findOne({ _id: user._id });
148-
expect(exist.name).toBe(user.name);
148+
expect(exist.n).toBe(user.name);
149149
});
150150

151151
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {

src/resolvers/__tests__/removeOne-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ describe('removeOne() ->', () => {
187187
});
188188
await expect(result).rejects.toMatchSnapshot();
189189
const exist = await UserModel.collection.findOne({ _id: user1._id });
190-
expect(exist.name).toBe(user1.name);
190+
expect(exist.n).toBe(user1.name);
191191
});
192192

193193
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {

0 commit comments

Comments
 (0)