Skip to content

Instantly share code, notes, and snippets.

@lackac
Created October 24, 2011 15:44
Show Gist options
  • Save lackac/1309345 to your computer and use it in GitHub Desktop.
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
{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