如何在validationcallback之前创build一个独特的slu slu water??

问题:我在NodeJS应用程序中使用Waterline作为OrientDB的ORM。 OrientDB使用数字ID,所以我不想让他们在我的url获得一个职位。 为什么? 因为只需增加ID就可以轻松查询完整的数据。

解决scheme:创build一个独特的slu。。

问:如何在asynchronouscallback中实现Waterline? 我需要这样的东西,但没有拿出一个解决scheme。 stream量可能是这样的:

  • 创buildslu。
  • 检查是否存在slu post的post
  • 如果不是,继续validation
  • 如果是的话,改变slu and并重新开始

以下是我目前使用的乐观解决scheme:

我的帮手类:

// databaseExtensions.js var _ = require('lodash'); function getUnique(property, value, separator, criteria){ separator = separator || '-'; criteria = criteria || {}; var searchObject = {}; searchObject[property] = { like: value + '%' }; _.mergeDefaults(searchObject, criteria); return this.find(searchObject) .then(function(models){ if(!models || models.length === 0) return value; // value is unique var values = _.pluck(models, property); return getUniqueFromArray(values, value, separator); }); } function getUniqueFromArray(existingValues, newValue, separator){ var valuesArray = _.clone(existingValues); var version = 2; // starting version var currentValue = newValue; var unique; while(version < 10000) { //just to be safe and we don't end up in a infinite loop unique = true; for(var i=0; i<valuesArray.length; i++){ if(currentValue === valuesArray[i]){ unique = false; valuesArray.splice(i, 1); break; } } if (unique) { return currentValue; } currentValue = newValue + separator + version; version++; } } module.exports.getUnique = getUnique; module.exports.getUniqueFromArray = getUniqueFromArray; 

我的模特定义:

 // post.model.js { //.. atributes: { //... urlSlug : { type : 'string', required : true, index : true } }, }, getUnique: require('path/to/databaseExtensions');.getUnique } 

在我的控制器中:

 // post.controller.js var slug = require('slug'); slug.defaults.mode ='pretty'; Post.getUnique('urlSlug', slug(post.title).toLowerCase(), '-') .then(function(uniqueSlug) { console.log('A new unique slug:', uniqueSlug); // assuming inserting title 'title', the results would be // title, title-2, title-3, etc }); 

在我的情况下,碰撞是不太可能的,所以我并不担心并发问题,其中2个模型同时出现在相同的标题。 但是,在成千上万的用户创buildpost的情况下,这可能是一个问题。

让我知道如果这没有帮助。

我想出了我自己的解决scheme使用asynchronous 。 最后,我决定不使用一个独特的slu as作为我的标识符,现在使用一个随机string(我称之为hash_id)和一个slu which(不一定是唯一的,只是在那里search引擎优化)的组合。 但是这个答案也包含了独特的slu solution的解决scheme。 所以我的url有这样的格式:

 http://example.com/posts/23hlj2l2/i_am_a_slug or http://example.com/posts/:hash_id/:slug 

我创build了一个辅助模块来进行string转换/创build。 他们只是处理这个,对ORM一无所知,或者如果一个值是唯一的。

ModelHelpers模块导出两个方法,一个是标准化一个input(像标题)来创build一个slu。。 它接受一个可选参数,这个参数是一个可以加到slug末尾的数字。

第二种方法创build一个随机字母数字string。 你可以传入一个string长度的参数。

 var ModelHelpers = function() { // Init } ModelHelpers.prototype.createSlugString = function(input_string, added_number) { added_number = typeof added_number !== 'undefined' ? added_number : ''; // First replace all whitespaces and '-' and make sure there are no double _ var clean_string = input_string.replace(/[\s\n\-]+/g, '_').replace(/_{2,}/g, '_'); // Replace Umlaute and make lowercase clean_string = clean_string.toLowerCase().replace(/ä/ig, 'ae').replace(/ö/ig, 'oe').replace(/ü/ig, 'ue'); // Replace any special characters and _ at the beginning or end clean_string = clean_string.replace(/[^\w]/g, '').replace(/^_+|_$/g, ''); // Only return the first 8 words clean_string = clean_string.split("_").slice(0,8).join("_"); // Add number if needed if(added_number !== '') { clean_string = clean_string + '_' + added_number.toString(); } return clean_string; } ModelHelpers.prototype.makeHashID = function(hash_length) { hash_length = typeof hash_length !== 'undefined' ? hash_length : 10; var text = ""; var possible = "abcdefghijklmnopqrstuvwxyz0123456789"; for( var i=0; i < hash_length; i++ ) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; } module.exports = ModelHelpers; 

我的解决scheme的下一部分是在validation和async结合之前使用Waterlines的生命周期callback。 这样我可以设置slug字段或哈希id字段为唯一的,它是在Waterlinevalidation之前创build的。 asynchronous是一个非常强大的工具,我只能build议看看它。 我正在使用这个方法:

同时(testing,FN,callback)

反复调用fn,而testing返回true。 停止时调用callback,或发生错误。

我创build了两个版本,一个是如果你需要一个随机string(hash_id),另一个是如果你想添加一个数字到你的slu end,如果它不是唯一的。

对于散列ID:

 var Waterline = require('Waterline'); var orientAdapter = require('sails-orientdb'); var ModelHelpers = require('../modules/model-helpers'); var async = require('async'); var mh = new ModelHelpers(); var Post = Waterline.Collection.extend({ identity: 'post', connection: 'myLocalOrient', attributes: { text: { type: 'text', required: true }, slug: { type: 'string' }, hash_id: { type: 'string', unique: true } }, // Lifecycle Callbacks beforeValidate: function(values, next) { var model_self = this; var keep_running = true; // Create first slug values.hash_id = mh.makeHashID(); values.slug = mh.createSlugString(values.text); async.whilst( function () { // execute whilst while other post has not been retrieved or while it matches a hash_id // in the database return keep_running; }, function (callback) { // search for post with this hash_id model_self.findOne().where({hash_id: values.hash_id}).then(function(op) { if(op === undefined) { // Nothing found, stop executing keep_running = false; } else { // Create new hash_id values.hash_id = mh.makeHashID(); } callback(); }); }, function (err) { // End the process // next(); is the callback of Waterlines' beforeValidate next(); } ); // End whilst } }); module.exports = Post; 

对于独特的slu::

 var Waterline = require('Waterline'); var orientAdapter = require('sails-orientdb'); var ModelHelpers = require('../modules/model-helpers'); var async = require('async'); var mh = new ModelHelpers(); var Post = Waterline.Collection.extend({ identity: 'post', connection: 'myLocalOrient', attributes: { text: { type: 'text', required: true }, slug: { type: 'string', unique: true }, hash_id: { type: 'string' } }, // Lifecycle Callbacks beforeValidate: function(values, next) { var model_self = this; var keep_running = true; var counter = 0; // we use this to add a number // Create first slug values.hash_id = mh.makeHashID(); values.slug = mh.createSlugString(values.text); async.whilst( function () { // execute whilst while other post has not been retrieved or while it matches a slug // in the database return keep_running; }, function (callback) { counter++; // search for post with this slug model_self.findOne().where({slug: values.slug}).then(function(op) { if(op === undefined) { // Nothing found, stop executing keep_running = false; } else { // Create new slug values.slug = mh.createSlugString(values.text, counter); } callback(); }); }, function (err) { // End the test next(); } ); // End whilst } }); module.exports = Post; 

这种方法的好处是,它只是继续运行,直到find一个独特的slug / hash_id,并且它不关心数字之间的差距(如果slug_2存在但不是slug_1)。 它也不关心你使用的数据库types。

如果偶然发生,两个进程在同一时刻写入相同的slu but,但仍然可能会导致问题,但这必须在几毫秒内完成。 而我认为防止这种情况的唯一办法就是locking桌子 – 如果我足够幸运有这个问题,我可以解决这个问题。