UNPKG

elasticmongooseq

Version:

Mongoose elasticsearch bindings

235 lines (186 loc) 7.17 kB
Q = require('q') _ = require('lodash') ### ElasticMongooseQModel represents the binding between a mongoose class and an elasticsearch type It handles commands no a collection and document level using the default options configured in ElasticMongooseQ. ### class ElasticMongooseQModel constructor: (elasticMongooseQ, options) -> @elasticMongooseQ = elasticMongooseQ # Mongoose requires a callback with sig (schema, options) to be passed to Schema.plugin # http://mongoosejs.com/docs/plugins.html # # @param {Object} options hash # @option {String} type - type of elasticsearch object # @option {Object} mapping - elasticsearch mapping for the object # @option {String} parentMethod - method to obtain a parent value (elasticsearch relationship) # @option {String} toObjectMethod - call a method to format document ready for elasticsearch # @option {Bool} autoSync - whether to automatically sync to elasticsearch # @option {Bool} autoRemove - whether to automatically remove from elasticsearch plugin: (schema, options = {}) => # Bind elasticsearch wide config into closure elasticModel = this elasticIndex = @elasticMongooseQ.elasticIndex elasticClient = @elasticMongooseQ.elasticClient logger = @elasticMongooseQ.logger # set up elasticsearch model elasticType = options.type parentMethod = options.parentMethod toObjectMethod = options.toObjectMethod elasticMapping = options.mapping || {} autoSync = options.autoSync || true autoRemove = options.autoRemove || true # Tell elasticMongooseQ about new type registered @elasticMongooseQ.elasticTypes.push elasticType # Create callbacks to sync data with elasticsearch if autoSync schema.post "save", (doc) -> doc.index().catch(logger.error) if elasticModel.autoRemove schema.post "remove", (doc) -> doc.unIndex() stdOpts = (opts = {}) -> defaultOptions = index: elasticIndex type: elasticType _.extend defaultOptions, opts buildObjectToIndex = (doc, callback) -> # If a toSearchObject method is defined then use that, else use toObject if toObjectMethod docOrPromise = doc[toObjectMethod]() else docOrPromise = doc.toObject() # If that method returns a promise then wait for that to resolve before indexing Q(docOrPromise) ### index to elasticsearch @param {Object} [optional] options hash with index options @param {Function} [optional] callback with node signature @returns {Promise} ### schema.methods.index = (options, callback) -> # If called with only callback param index(callback) if typeof options == 'function' callback = options options = {} # If called with no params index() else if typeof options == 'undefined' options = {} options.id ||= "#{@id}" if parentMethod options.parent ||= "#{@[parentMethod]()}" indexDoc = (doc) -> options.body = doc indexObject = stdOpts options elasticClient.index(indexObject) buildObjectToIndex(this).then(indexDoc).nodeify(callback) ### Deletes a item from the index @param {Function} callback with node signature @returns {Promise} ### schema.methods.unIndex = (callback) -> opts = stdOpts(id: "#{@id}") elasticClient.delete(opts).nodeify(callback) ### Sync mongo data into elasticsearch @param {Object} options @option {Object} query - mongodb query object @param {Function} callback @returns {Promise} ### schema.statics.syncSearchIndex = (options, callback) -> # If called with only callback param index(callback) if typeof options == 'function' callback = options options = {} # If called with no params index() else if typeof options == 'undefined' options = {} batchSize = options.batchSize ||= 1000 query = options.query || {} deferred = Q.defer() stream = @find(query).stream() finished = false indexQueue = [] stream.on 'data', (doc) -> indexQueue.push doc if finished or indexQueue.length >= batchSize stream.pause() processQueue(indexQueue) indexQueue = [] stream.on "close", () -> finished = true processQueue(indexQueue) indexQueue = [] stream.on "error", deferred.reject processQueue = (docsToIndex) -> logger.info "processing #{docsToIndex.length} docs\n memory used is #{process.memoryUsage().heapUsed}" bulkBody = [] indexDocuments = () -> docsToIndex = [] # done with index queue so clear up elasticClient.bulk(body: bulkBody) checkIfComplete = (bulkResult) -> bulkBody = [] # done with bulk body so reset if finished deferred.resolve(true) else stream.resume() buildBulkBody = -> Q.all docsToIndex.map (doc) -> buildObjectToIndex(doc).then (itemToIndex) -> options = index: _index: elasticIndex _type: elasticType _id: doc.id if parentMethod options.index._parent = "#{doc[parentMethod]()}" doc = null # done with doc so clear up memory bulkBody.push options bulkBody.push itemToIndex buildBulkBody().then(indexDocuments).then(checkIfComplete) deferred.promise.nodeify(callback) ### Search elasticsearch @param {Object} query - elasticsearch query options @param {Function} callback @returns {Promise} ### schema.statics.search = (query, callback) -> queryOptions = stdOpts(body: query) elasticClient.search(queryOptions).nodeify(callback) ### Create an elasticsearch mapping for this model @param {Function} callback @returns {Promise} ### schema.statics.putMapping = (callback) -> mappingBody = {} mappingBody[elasticType] = elasticMapping mappingOptions = stdOpts body: mappingBody ignoreConflicts: true elasticClient.indices.putMapping(mappingOptions).nodeify(callback) ### Get the elasticsearch mapping for this model @param {Function} callback @returns {Promise} ### schema.statics.getMapping = (callback) -> elasticClient.indices.getMapping(stdOpts()).nodeify(callback) ### Delete the elasticsearch mapping for this model @param {Function} callback @returns {Promise} ### schema.statics.deleteMapping = (callback) -> elasticClient.indices.deleteMapping(stdOpts()).nodeify(callback) ### Clear the contents of the elasticsearch index @param {Function} callback @returns {Promise} ### schema.statics.clearIndex = (callback) -> opts = stdOpts(q: '*') elasticClient.deleteByQuery(opts).nodeify(callback) module.exports = ElasticMongooseQModel