Created
October 24, 2011 15:44
-
-
Save lackac/1309345 to your computer and use it in GitHub Desktop.
Patches mongoose with a method to separate reference properties from their referenced object accessors
This file contains hidden or 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
{Model} = module.exports = mongoose = require 'mongoose' | |
# Make populated references available as separate properties | |
# e.g. | |
# if `ProductSchema` has `brand_id: { type: String, ref: 'Brand' }`, | |
# `Product.findById(some_id).populate('brand').run(cb)` will populate | |
# using `brand_id` but the populated brand object will be available as | |
# `product.brand`. | |
originalInit = Model::init | |
Model::init = (doc, query, fn) -> | |
# skip this thing if there's nothing to populate | |
return originalInit.apply(this, arguments) unless query?.options.populate | |
# find correct attribute names | |
transformPopulateOptions(query.options.populate, @schema) | |
# check whether we really have stuff to populate | |
if populate = @_getPopulationKeys(query) | |
fix_references = {} | |
for name, {sub} of populate | |
if m = name.match(/^(.*)_id$/) | |
fix_references[m[1]] = name | |
else if sub | |
for sub_name of sub when m = sub_name.match(/^(.*)_id$/) | |
fix_references[name] ||= {} | |
fix_references[name][m[1]] = sub_name | |
@once 'init', -> | |
for prop, ref of fix_references | |
do (prop, ref) => | |
if typeof ref is "string" | |
defineAccessor(this, prop, ref) | |
else if embedded = @[prop] | |
for sub_prop, sub_ref of ref | |
if Array.isArray(embedded) | |
defineAccessor(el, sub_prop, sub_ref) for el in embedded | |
else | |
defineAccessor(embedded, sub_prop, sub_ref) | |
originalInit.apply(this, arguments) | |
transformPopulateOptions = (options, schema) -> | |
for name, populate of options | |
# shortcut if property with name exists | |
continue if schema.path(name) | |
# check whether we have #{name}_id property in this schema | |
if schema.path("#{name}_id") | |
options["#{name}_id"] = populate | |
delete options[name] | |
# look for property in embedded docs | |
else | |
pieces = name.split('.') | |
for piece, i in pieces | |
path = pieces.slice(0, i).join('.') | |
if (path_schema = schema.path(path)) and path_schema.caster | |
path_schema = path_schema.schema | |
sub_path = pieces.slice(i).join('.') | |
if path_schema.path(sub_path) | |
# sub path is ok | |
else if path_schema.path("#{sub_path}_id") | |
options["#{name}_id"] = populate | |
delete options[name] | |
break | |
defineAccessor = (obj, prop, ref) -> | |
return unless (doc = obj._doc[ref]) and doc._id | |
obj._doc[ref] = doc._id | |
Object.defineProperty obj, prop, | |
enumerable: true | |
get: -> doc | |
set: (val) -> | |
old_val = @[ref] | |
@[ref] = val | |
if @[ref]? and @[ref] isnt old_val | |
doc = val | |
else if not @[ref]? | |
doc = null |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment