Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reference model from within validator on nested schema #3589

Closed
viveleroi opened this issue Nov 19, 2015 · 9 comments
Closed

Reference model from within validator on nested schema #3589

viveleroi opened this issue Nov 19, 2015 · 9 comments
Milestone

Comments

@viveleroi
Copy link

I'm trying to find a way to get a reference to the parent model of a nested schema from inside the validator.

I maintain a plugin which queries a collection for unique records before mongo returns a duplicate key error, so these validators are built dynamically.

I have a feeling it's not possible, and I need to check schema.nested and use some custom logic for these nested documents.

When the validator is called on a nested schema, the validator's this is Document:

// this.constructor.name = "Document"
// typeof this.model = "undefined"

Yet for a validator directly on the parent, it's a model:

// this.constructor.name = "model"
// typeof this.model = "function"

An example schema:

var ContactSchema = new mongoose.Schema({
    email: {
        type: String,
        index: true,
        unique: true
    }
});
ContactSchema.plugin(uniqueValidator);

var CompanySchema = new mongoose.Schema({
    name: String,
    contact: ContactSchema
});
// CompanySchema.plugin(uniqueValidator);
@vkarpov15
Copy link
Collaborator

First of all, I'd strongly advise against using mongoose-unique-validator, it has a fundamental race condition that makes it virtually useless for enforcing unique constraints.

That being said, try this.ownerDocument().model. The ownerDocument() function is only defined on sub-docs, and .model is only defined on root docs.

@viveleroi
Copy link
Author

I've taken over maintenance of that very plugin from its original owner, what race condition are you referring to? I've never noticed any race conditions, at least with my rewrite of it.

@vkarpov15
Copy link
Collaborator

Because mongoose-unique-validator relies on async operations like .count() to verify whether a document exists in the database, it's possible for two .count() calls to execute roughly at the same time, both get 0 back, and then both insert into MongoDB. You'd need to do some locking operation to make it work properly in the general case.

The only way to work around this is to use only one process and set your mongodb connection pool size to 1, but that limits the amount of app servers you can run and will only work so long as mongodb retains its legacy behavior of blocking operations on a per-connection basis.

@viveleroi
Copy link
Author

It sounds like there's no feasible way around it. I think for the majority of our users use-cases this will be an edge case, I've personally never had a scenario in which duplicate data was written to the collection while an existing count/query was returning 0.

I'll be sure to make this clear in readme though, thanks.

@viveleroi
Copy link
Author

this.ownerDocument isn't available in this context so I'll need to figure something else out.

@vkarpov15
Copy link
Collaborator

The fact that ownerDocument isn't there is a bug IMO, I'll re-open this.

@vkarpov15 vkarpov15 reopened this Nov 19, 2015
@vkarpov15 vkarpov15 added this to the 4.2.7 milestone Nov 19, 2015
@vkarpov15 vkarpov15 reopened this Nov 20, 2015
@vkarpov15
Copy link
Collaborator

Woops wrong issue number, 6c98eb4 was meant to be a fix for #3481

vkarpov15 added a commit that referenced this issue Nov 20, 2015
@vichle
Copy link
Contributor

vichle commented Jul 18, 2016

It seems like this bug was reintroduced, or that it was never resolved for sub-subdocuments, see example below:

mongoose = require 'mongoose'
mongoose.connect 'mongodb://localhost/mongotest'

print = -> console.log 'name:', @name, 'ownerDocument:', @ownerDocument().name

grandChildSchema = new mongoose.Schema
  name: String
grandChildSchema.methods =
  print: print

childSchema = new mongoose.Schema
  name: String
  child: grandChildSchema
childSchema.methods =
  print: print

parentSchema = new mongoose.Schema
  name: String
  children: [childSchema]

Parent = mongoose.model 'Parent', parentSchema

p = new Parent
  name: 'Parent Parentson'
  children: [
    {
      name: 'Child Parentson'
      child:
        name: 'Grandchild Parentson'
    }
  ]

p.children[0].print()
p.children[0].child.print()

Received output:

Child Parentson ownerDocument: Parent Parentson
Grandchild Parentson ownerDocument: Child Parentson

Expected output

Child Parentson ownerDocument: Parent Parentson
Grandchild Parentson ownerDocument: Parent Parentson

Edit
Shorter example.
This only seems to only happen if parent has an array of children, not if childSchema is a single subdocument.
Edit2
It seems like it also happens if childSchema is a single document and has an array of grandSchildSchema.
Edit3
Sorry about the CoffeeScript, I didn't read CONTRIBUTING.md until I started fixing this 😊

@vichle
Copy link
Contributor

vichle commented Jul 18, 2016

I managed to fix it (I think) for the first case where parent has an array of childSchema by changing :

Subdocument.prototype.ownerDocument = function() {
  if (this.$__.ownerDocument) {
    return this.$__.ownerDocument;
  }
  var parent = this.$parent;
  if (!parent) {
    return this;
  }

  while (parent.$parent) {
    parent = parent.$parent;
  }
  this.$__.ownerDocument = parent;
  return this.$__.ownerDocument;
};

to

Subdocument.prototype.ownerDocument = function() {
  if (this.$__.ownerDocument) {
    return this.$__.ownerDocument;
  }
  var parent = this.$parent;
  if (!parent) {
    return this;
  }

  while (parent.$parent || parent.__parent) {
    parent = parent.$parent || parent.__parent;
  }
  this.$__.ownerDocument = parent;
  return this.$__.ownerDocument;
};

I have not run the tests with this fix.
The corresponding change for EmbeddedDocument does not seems to work. Would appreciate if someone with more insight into the project could try it out? @vkarpov15

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants