Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit, adding slugify with unique slugs, and array of ancest…
…ors tree.
- Loading branch information
Marius Kubilius
committed
Jan 7, 2013
0 parents
commit bc7b9ec
Showing
9 changed files
with
467 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Numerous always-ignore extensions | ||
*.diff | ||
*.err | ||
*.orig | ||
*.log | ||
*.rej | ||
*.swo | ||
*.swp | ||
*.vi | ||
*~ | ||
|
||
# OS or Editor folders | ||
.DS_Store | ||
.cache | ||
.project | ||
.settings | ||
nbproject | ||
thumbs.db | ||
|
||
# Logs | ||
.log | ||
.pid | ||
.sock | ||
.monitor | ||
|
||
# Dreamweaver added files | ||
_notes | ||
dwsync.xml | ||
|
||
# Komodo | ||
*.komodoproject | ||
.komodotools | ||
|
||
# Folders to ignore | ||
node_modules | ||
.hg | ||
.svn | ||
publish | ||
.idea | ||
_dev |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require('directory')(__dirname + '/lib/', function (fn, filename) { | ||
module.exports[filename] = fn | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/** | ||
* Mongoose extension implementing ancestor tree as defined in the link below. | ||
* @url http://docs.mongodb.org/manual/tutorial/model-tree-structures-with-ancestors-array/ | ||
* @author Marius Kubilius | ||
* @todo add methods and statics dealing with the tree. | ||
*/ | ||
var ObjectId = require('mongoose').Schema.ObjectId; | ||
|
||
ancestorTree = function(schema){ | ||
|
||
var fields = {}; | ||
if (!schema.paths.parent) { | ||
fields.parent = { | ||
type: ObjectId, | ||
set : function(val) { | ||
if(typeof(val) === "object" && val._id) { | ||
return val._id; | ||
} | ||
return val; | ||
}, | ||
index: true | ||
} | ||
} | ||
|
||
if (!schema.paths.ancestors) { | ||
fields.ancestors = [{type: ObjectId, index: true}]; | ||
} | ||
|
||
schema.add(fields); | ||
|
||
var createAncestors = function(ancestors, parent) { | ||
return ancestors.push(parent); | ||
} | ||
|
||
schema.pre('save', function (next) { | ||
var parentModified = this.isDirectModified('parent'); | ||
var self = this; | ||
|
||
if (self.isNew || parentModified) { | ||
if(!self.parent && self.isNew){ | ||
return next(); | ||
} | ||
self.collection.findOne({ _id : self.parent }, function(err, doc) { | ||
if(err) return next(err); | ||
var oldAncestorsLength = self.ancestors.length; | ||
if(doc) { | ||
self.ancestors = doc.ancestors; | ||
// @todo find atomic operation for that. | ||
self.ancestors.nonAtomicPush(self.parent); | ||
//self.markModified('ancestors'); | ||
} | ||
else { | ||
self.ancestors = []; | ||
self.markModified('ancestors'); | ||
} | ||
if(!self.isNew && parentModified){ | ||
|
||
self.collection.find({ ancestors : self._id }, function(err, cursor) { | ||
if(err) return next(err); | ||
var stream = cursor.stream(); | ||
stream.on('data', function (doc) { | ||
//console.log(self.ancestors.concat(doc.ancestors.slice(oldAncestorsLength))); | ||
var newPath = self.ancestors.concat(doc.ancestors.slice(oldAncestorsLength)); | ||
self.collection.update({ _id : doc._id }, { $set : { ancestors : newPath } }, function(err){ | ||
if(err) return next(); | ||
}); | ||
}); | ||
stream.on('close', function() { | ||
next(); | ||
}); | ||
stream.on('error', function(err) { | ||
next(err); | ||
}); | ||
}); | ||
} | ||
else{ | ||
next(); | ||
} | ||
}); | ||
} | ||
else{ | ||
next(); | ||
} | ||
}); | ||
} | ||
|
||
module.exports = ancestorTree; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
/** | ||
* Mongoose extension which makes sure that the slugs are unique no matter what. | ||
* Has minimum configuration operations, as it is suposed to be used for in house | ||
* developement. | ||
* @author Marius Kubilius <marius.kubilius@gmail.com> | ||
* @param schema | ||
* @todo add lithuanian accents. | ||
*/ | ||
slugify = function(schema) { | ||
|
||
//define defaults | ||
var fr = 'àáâãäåçèéêëìíîïñðóòôõöøùúûüýÿ' // Accent chars to find | ||
var to = 'aaaaaaceeeeiiiinooooooouuuuyy' // Accent replacement | ||
var fields = {}; | ||
|
||
//if not defined define schema for title and slug. | ||
|
||
if (!schema.paths.slug) { | ||
fields.slug = { | ||
type: String | ||
, index:{unique: true, sparse: true} | ||
} | ||
} | ||
|
||
if (!schema.paths.title) { | ||
fields.title = String; | ||
} | ||
|
||
schema.add(fields); | ||
|
||
['static', 'method'].forEach(function (method) { | ||
schema[method]('slugify', function (str) { | ||
str = str | ||
.replace(/^\s+|\s+$/g, '') | ||
.toLowerCase(); | ||
|
||
//replace all illegal characters and accents | ||
for (var i=0; i<fr.length; i++) { | ||
str = str.replace(new RegExp(fr.charAt(i), 'g'), to.charAt(i)); | ||
} | ||
return str | ||
.replace(/[^a-z0-9 -]/g, '') | ||
.replace(/\s+/g, '-'); | ||
}) | ||
}) | ||
|
||
// pre save check whether slug is modified ;) | ||
// Extract the slug on save, optionally overriding a previous value | ||
schema.pre('save', function (next) { | ||
var self = this; | ||
var slugChanged = self.isDirectModified('slug'); | ||
//check for duplicated slugs. | ||
var checkDupes = function(self, oldSlug, i){ | ||
self.collection.count({slug: self.slug}, function(err, count) { | ||
if (err) return next(err); | ||
if(count > 0) { | ||
self.slug = oldSlug + '-' + i; | ||
i++ | ||
checkDupes(self, oldSlug, i); | ||
} | ||
else { | ||
next(); | ||
} | ||
}); | ||
} | ||
if (slugChanged) { | ||
self.slug = self.slugify(self.slug); | ||
checkDupes(self, self.slug, 1); | ||
} | ||
if (!self.slug) { | ||
self.slug = self.slugify(self.title); | ||
checkDupes(self, self.slug, 1); | ||
} | ||
else { | ||
next(); | ||
} | ||
|
||
|
||
}); | ||
|
||
|
||
|
||
} | ||
|
||
module.exports = slugify; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name" : "lbg-mongoose-utils", | ||
"version" : "0.0.1", | ||
"description" : "a collection of plugins used in my projects for mongoose", | ||
"main" : "index.js", | ||
"bin" : {}, | ||
"directories" : { | ||
"test" : "test" | ||
}, | ||
"scripts" : { | ||
"test": "make test && make clean" | ||
}, | ||
"dependencies" : { | ||
"mongoose": "~3.5.x" | ||
}, | ||
"devDependencies" : { | ||
"mocha": "~0.10.0" | ||
}, | ||
"keywords" : [ | ||
"mongoose", | ||
"mongoose 3.5.x", | ||
"slugs", | ||
"unique-slugs", | ||
"array of ancestors", | ||
"tree" | ||
], | ||
"author" : { | ||
"name" : "Marius Kubilius", | ||
"email" : "marius.kubilius@gmail.com", | ||
"url" : "https://www.littleboygenius.com" | ||
}, | ||
"license" : "MIT" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
A small collection of mongoose extensions to be used in my projects. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
//dependencies | ||
var assert = require('assert'); | ||
var mongoose = require('mongoose'); | ||
//mongoose.set('debug', true); | ||
var slugify = require('../lib/ancestorTree'); | ||
var common = require('./utils/common'); | ||
var db = common.db; | ||
var Schema = mongoose.Schema; | ||
var ObjectId = Schema.ObjectId; | ||
|
||
describe('ancestorTree', function () { | ||
var MockSchema = new Schema(); | ||
|
||
describe('#default()', function() { | ||
MockSchema.plugin(ancestorTree); | ||
var MockModel = db.model('ancestorMock', MockSchema); | ||
var parent = new MockModel({}); | ||
var child = new MockModel({parent: parent._id}); | ||
var subChild = new MockModel({parent: child._id}); | ||
|
||
before(function() { | ||
MockModel.remove(function(err) { | ||
assert.strictEqual(err, null); | ||
}); | ||
}); | ||
|
||
it('should have custom properties set', function (done) { | ||
assert.strictEqual(typeof MockSchema.paths.parent, 'object'); | ||
assert.strictEqual(typeof MockSchema.paths.ancestors, 'object'); | ||
done(); | ||
}); | ||
|
||
it('should save and set hierarchy where aplicable', function (done) { | ||
parent.save(function(err, doc){ | ||
assert.strictEqual(err, null); | ||
assert.strictEqual(typeof doc.parent, 'undefined'); | ||
assert.strictEqual(typeof doc.ancestors, 'object'); | ||
assert.strictEqual(doc.parent, undefined); | ||
assert.strictEqual(doc.ancestors.length, 0); | ||
child.save(function(err,doc){ | ||
assert.strictEqual(err, null); | ||
assert.strictEqual(typeof doc.parent, 'object'); | ||
assert.strictEqual(doc.parent, parent._id); | ||
assert.strictEqual(typeof doc.ancestors, 'object'); | ||
assert.strictEqual(doc.ancestors.length, 1); | ||
assert.strictEqual(doc.ancestors[0], parent._id); | ||
subChild.save(function(err, doc){ | ||
assert.strictEqual(err, null, 'should be no errors thrown'); | ||
assert.strictEqual(doc.ancestors.length, 2, 'should be length of 2'); | ||
assert.strictEqual(doc.ancestors[1], child._id, 'second item should be last added'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
}); | ||
|
||
describe('#update()', function(){ | ||
MockSchema.plugin(ancestorTree); | ||
var MockModel = db.model('ancestorMock', MockSchema); | ||
var p = new MockModel({}); | ||
var c = new MockModel({parent: p._id}); | ||
var sc = new MockModel({parent: c._id}); | ||
var ap = new MockModel({}); | ||
var ac = new MockModel({parent: ap._id}); | ||
var asc = new MockModel({parent: ac._id}); | ||
|
||
before(function() { | ||
MockModel.remove(function(err) { | ||
assert.strictEqual(err, null); | ||
}); | ||
}); | ||
|
||
it ('should save and update ancestors.', function (done) { | ||
p.save(function(err,doc){ | ||
assert.strictEqual(err, null, 'item without parent should save.'); | ||
c.save(function(err, doc){ | ||
sc.save(function(err,doc) { | ||
ap.save(function(err,doc) { | ||
ac.save(function(err,doc) { | ||
asc.save(function(err,doc) { | ||
assert.strictEqual(err, null, 'should be no errors while saving.'); | ||
ac.parent = c; | ||
ac.save(function(err,doc){ | ||
assert.strictEqual(err, null, 'should be no errors while saving.'); | ||
MockModel.findOne({_id: asc._id}, function(err, doc){ | ||
assert.strictEqual(doc.ancestors[0].toString(), p._id.toString(), 'hierarchy should be updated to the new tree'); | ||
assert.strictEqual(doc.ancestors[1].toString(), c._id.toString(), 'hierarchy should be updated in children'); | ||
assert.strictEqual(doc.ancestors.length, 3) | ||
c.parent = undefined; | ||
c.save(function(err,doc){ | ||
assert.strictEqual(err, null); | ||
done(); | ||
}); | ||
}); | ||
}) | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
}); | ||
|
||
}); | ||
|
||
}); |
Oops, something went wrong.