Skip to content
This repository was archived by the owner on Jan 19, 2024. It is now read-only.

Commit 09216b4

Browse files
borodayevnodkz
authored andcommitted
feat: add mongoose discriminators support (#47, thanks @FrankAst )
* wip * test: add tests for `withDiscriminators` case * refactor(index): small fixes * refactor: define types as a function only * refactor: fix test, generate mapping if discriminators are enabled * refactor: add cachedMappings * docs: update README.md * ci: change node 9 => 10
1 parent ca0c7d9 commit 09216b4

File tree

4 files changed

+198
-44
lines changed

4 files changed

+198
-44
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ cache:
66
- node_modules
77

88
node_js:
9-
- "9"
9+
- "10"
1010
- "8"
1111

1212
addons:

README.md

Lines changed: 73 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This plugin is compatible with Elasticsearch version 2 and 5.
1919
- [Unsetting fields](#unsetting-fields)
2020
- [Adding fields](#adding-fields)
2121
- [Change fields value](#change-fields-value)
22+
- [Using with mongoose discriminators](#using-with-mongoose-discriminators)
2223
- [Mapping](#mapping)
2324
- [Creating mappings on-demand](#creating-mappings-on-demand)
2425
- [Queries](#queries)
@@ -44,7 +45,7 @@ This plugin handle both callback and promise syntaxes. It uses the mongoose Prom
4445

4546
## Installation
4647

47-
The latest version of this package will be as close as possible to the latest `elasticsearch` and `mongoose` packages.
48+
The latest version of this package will be as close as possible to the latest `elasticsearch` and `mongoose` packages.
4849

4950
```bash
5051
npm install --save mongoose-elasticsearch-xp
@@ -73,7 +74,7 @@ The examples below use the version 5 syntax.
7374
Options are:
7475

7576
* `index` - the index in Elasticsearch to use. Defaults to the collection name.
76-
* `type` - the type this model represents in Elasticsearch. Defaults to the model name.
77+
* `type` - the type this model represents in Elasticsearch. Defaults to the model name. It may be a function `(modelName) => typeName`.
7778
* `client` - an existing Elasticsearch `Client` instance.
7879
* `hosts` - an array hosts Elasticsearch is running on.
7980
* `host` - the host Elasticsearch is running on.
@@ -100,8 +101,8 @@ var mongoose = require('mongoose');
100101
var mexp = require('mongoose-elasticsearch-xp');
101102

102103
var UserSchema = new mongoose.Schema({
103-
name: String,
104-
email: String,
104+
name: String,
105+
email: String,
105106
city: String
106107
});
107108

@@ -110,18 +111,18 @@ UserSchema.plugin(mexp);
110111
var User = mongoose.model('User', UserSchema);
111112
```
112113

113-
This will by default simply use the collection name as the index while using the model name itself as the type.
114-
So if you create a new User object and save it, you can see it by navigating to http://localhost:9200/users/user/_search
115-
(this assumes Elasticsearch is running locally on port 9200).
114+
This will by default simply use the collection name as the index while using the model name itself as the type.
115+
So if you create a new User object and save it, you can see it by navigating to http://localhost:9200/users/user/_search
116+
(this assumes Elasticsearch is running locally on port 9200).
116117

117-
The default behavior is all fields get indexed into Elasticsearch.
118+
The default behavior is all fields get indexed into Elasticsearch.
118119
This can be a little wasteful especially considering that the document is now just being duplicated between mongodb and Elasticsearch so you should consider opting to index only certain fields by specifying `es_indexed` on the fields you want to store:
119120

120121

121122
```javascript
122123
var UserSchema = new mongoose.Schema({
123-
name: {type: String, es_indexed: true},
124-
email: String,
124+
name: {type: String, es_indexed: true},
125+
email: String,
125126
city: String
126127
});
127128

@@ -177,7 +178,7 @@ User
177178
});
178179
```
179180

180-
To connect to more than one host, you can use an array of hosts.
181+
To connect to more than one host, you can use an array of hosts.
181182

182183
```javascript
183184
MyModel.plugin(mexp, {
@@ -202,8 +203,8 @@ MyModel.plugin(mexp, {
202203
## Indexing
203204

204205
### Saving a document
205-
The indexing takes place after saving inside the mongodb and is a deferred process.
206-
One can check the end of the indexion catching `es-indexed` event.
206+
The indexing takes place after saving inside the mongodb and is a deferred process.
207+
One can check the end of the indexion catching `es-indexed` event.
207208
This event is emitted both from the document and the model (which make unit tests easier).
208209

209210
```javascript
@@ -223,15 +224,15 @@ In order to index nested models you can refer following example.
223224

224225
```javascript
225226
var CommentSchema = new mongoose.Schema({
226-
title: String,
227-
body: String,
227+
title: String,
228+
body: String,
228229
author: String
229230
});
230231

231232
var UserSchema = new mongoose.Schema({
232-
name: {type: String, es_indexed: true},
233-
email: String,
234-
city: String,
233+
name: {type: String, es_indexed: true},
234+
email: String,
235+
city: String,
235236
comments: {type: [CommentSchema], es_indexed: true}
236237
});
237238

@@ -268,10 +269,10 @@ var CitySchema = new mongoose.Schema({
268269
var City = mongoose.model('City', CitySchema);
269270

270271
var UserSchema = new mongoose.Schema({
271-
name: String,
272+
name: String,
272273
city: {
273-
type: mongoose.Schema.Types.ObjectId,
274-
ref: 'City',
274+
type: mongoose.Schema.Types.ObjectId,
275+
ref: 'City',
275276
es_type: {
276277
name: {
277278
es_type: 'string'
@@ -300,7 +301,7 @@ var User = mongoose.model('User', UserSchema);
300301

301302

302303
### Indexing An Existing Collection
303-
Already have a mongodb collection that you'd like to index using this plugin?
304+
Already have a mongodb collection that you'd like to index using this plugin?
304305
No problem! Simply call the `esSynchronize` method on your model to open a mongoose stream and start indexing documents individually.
305306

306307
```javascript
@@ -407,7 +408,7 @@ If [dynamic-scripting](https://www.elastic.co/guide/en/elasticsearch/reference/2
407408
408409
409410
### Adding fields
410-
`es_extend` allows to add some fields which does not exist in the mongoose schema.
411+
`es_extend` allows to add some fields which does not exist in the mongoose schema.
411412
It is defined in the options of the schema definition.
412413
When adding some fields, `es_type` and `es_value` are mandatories.
413414
@@ -436,7 +437,7 @@ The `es_value` parameter can be either a value or a function returning a value,
436437
### Change fields value
437438
`es_value` allows to replace the value of a field. It can be either a value or a function which will return the value to index.
438439
If the type changes, it is mandatory to set the correct `es_type`.
439-
440+
440441
```javascript
441442
var TagSchema = new mongoose.Schema({
442443
_id: false,
@@ -447,7 +448,7 @@ var UserSchema = new mongoose.Schema({
447448
name: String,
448449
xyz: {
449450
type: Number,
450-
es_value: 123 // <= whatever the model.xyz value is, the xyz indexed will be 123 in ES
451+
es_value: 123 // <= whatever the model.xyz value is, the xyz indexed will be 123 in ES
451452
},
452453
tags: {
453454
type: [TagSchema],
@@ -487,35 +488,66 @@ context contains:
487488
* `container` the container of the original value (which is equal to the `document` when it is not a nested object)
488489
* `field` the key name
489490
491+
### Using with mongoose discriminators
492+
493+
You may save discriminator models' data in different Elasticsearch types with different mappings. To make it possible you should provide `type` option as a function. You will get `modelName` as an argument and must return type name for Elasticsearch.
494+
495+
```js
496+
// define base Schema with base Model
497+
const BaseSchema = new mongoose.Schema({
498+
name: String,
499+
});
500+
const BaseModel = mongoose.model('Base', BaseSchema);
501+
502+
// define discriminator models
503+
const UserModel = BaseModel.discriminator('User', new mongoose.Schema({
504+
age: Number,
505+
}));
506+
507+
const AdminModel = BaseModel.discriminator('Admin', new mongoose.Schema({
508+
access: Boolean,
509+
}));
510+
511+
// add mexp plugin to the base Schema, with `type` as a function
512+
BaseSchema.plugin(mexp, {
513+
index: 'user',
514+
type: kind => {
515+
if (kind === 'User') return 'userType';
516+
if (kind === 'Admin') return 'adminType';
517+
return 'base';
518+
},
519+
});
520+
```
521+
490522
491523
## Mapping
492524
493-
Schemas can be configured to have special options per field. These match with the existing [mapping parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html) defined by Elasticsearch with the only difference being they are all prefixed by `es_`.
525+
Schemas can be configured to have special options per field. These match with the existing [mapping parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html) defined by Elasticsearch with the only difference being they are all prefixed by `es_`.
494526
495527
So for example. If you wanted to index a book model and have the boost for title set to 2.0 (giving it greater priority when searching) you'd define it as follows:
496528
497529
```javascript
498530
var BookSchema = new mongoose.Schema({
499-
title: {type: String, es_boost: 2.0},
500-
author: {type: String, es_null_value: "Unknown Author"},
501-
publicationDate: {type: Date, es_type: 'date'}
502-
});
531+
title: {type: String, es_boost: 2.0},
532+
author: {type: String, es_null_value: "Unknown Author"},
533+
publicationDate: {type: Date, es_type: 'date'}
534+
});
503535

504536
```
505537
This example uses a few other mapping fields... such as null_value and type (which overrides whatever value the schema type is, useful if you want stronger typing such as float).
506538
507-
There are various mapping options that can be defined in Elasticsearch. Check out [https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html/](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) for more information.
539+
There are various mapping options that can be defined in Elasticsearch. Check out [https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html/](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) for more information.
508540
509541
### Creating Mappings On Demand
510542
511543
You can do on-demand create a mapping using the `esCreateMapping` function.
512544
513545
Creating the mapping is a one time operation and can be done as follows:
514546
515-
```javascript
547+
```javascript
516548
var UserSchema = new mongoose.Schema({
517-
name: String,
518-
email: String,
549+
name: String,
550+
email: String,
519551
city: String
520552
});
521553

@@ -540,19 +572,19 @@ User
540572

541573
```
542574
543-
You'll have to manage whether or not you need to create the mapping, mongoose-elasticsearch-xp will make no assumptions and simply attempt to create the mapping.
544-
If the mapping already exists, an Exception detailing such will be populated in the `err` argument.
575+
You'll have to manage whether or not you need to create the mapping, mongoose-elasticsearch-xp will make no assumptions and simply attempt to create the mapping.
576+
If the mapping already exists, an Exception detailing such will be populated in the `err` argument.
545577
546578
## Queries
547-
The full query DSL of Elasticsearch is exposed through the `esSearch` method.
579+
The full query DSL of Elasticsearch is exposed through the `esSearch` method.
548580
For example, if you wanted to find all people between ages 21 and 30:
549581
550582
```javascript
551583
Person
552584
.esSearch({
553585
range: {
554586
age: {
555-
from: 21,
587+
from: 21,
556588
to: 30
557589
}
558590
}
@@ -588,7 +620,7 @@ Person
588620
```
589621
590622
### Hydration
591-
By default objects returned from performing a search will be the objects as is in Elasticsearch.
623+
By default objects returned from performing a search will be the objects as is in Elasticsearch.
592624
This is useful in cases where only what was indexed needs to be displayed (think a list of results) while the actual mongoose object contains the full data when viewing one of the results.
593625
594626
However, if you want the results to be actual mongoose objects you can provide {hydrate: true} as the second argument to a search call.
@@ -675,7 +707,7 @@ User
675707
676708
677709
### Getting only Ids
678-
A variant to hydration may be to get only ids instead of the complete Elasticsearch result.
710+
A variant to hydration may be to get only ids instead of the complete Elasticsearch result.
679711
Using `idsOnly` will return the ids cast in mongoose ObjectIds.
680712
681713
```javascript
@@ -687,7 +719,7 @@ User
687719
```
688720
689721
## Count
690-
The [count API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html) is available using the `esCount` function.
722+
The [count API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html) is available using the `esCount` function.
691723
It handle the same queries as the `esSearch` method (string query, full query...).
692724
693725
```javascript

lib/index.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ const mongoose = require('mongoose');
99
module.exports = function(schema, options, version) {
1010
// clone main level of options (does not clone deeper)
1111
options = utils.highClone(options);
12+
const cachedMappings = new Map();
1213

14+
let generateType;
15+
if (typeof options.type === 'function') {
16+
generateType = options.type;
17+
}
1318
/**
1419
* Retrieve model options to ElasticSearch
1520
* static function
@@ -19,7 +24,10 @@ module.exports = function(schema, options, version) {
1924
if (!options.index) {
2025
options.index = this.collection.name;
2126
}
22-
if (!options.type) {
27+
28+
if (generateType) {
29+
options.type = generateType(this.modelName || this.constructor.modelName);
30+
} else if (!options.type) {
2331
options.type = utils.lcFirst(
2432
this.modelName || this.constructor.modelName
2533
);
@@ -41,10 +49,13 @@ module.exports = function(schema, options, version) {
4149
options.bulker = new Bulker(options.client, options.bulk);
4250
}
4351

44-
if (!options.mapping) {
52+
if (cachedMappings.has(this.schema)) {
53+
options.mapping = cachedMappings.get(this.schema);
54+
} else {
4555
options.mapping = Object.freeze({
4656
properties: generateMapping(this.schema, version),
4757
});
58+
cachedMappings.set(this.schema, options.mapping);
4859
}
4960

5061
return utils.highClone(options);

0 commit comments

Comments
 (0)