troubleshooting Question

What is Discriminator and Why is is Problem in this Case?

Avatar of Bruce Gust
Bruce GustFlag for United States of America asked on
DatabasesJavaScriptNoSQL DatabasesMongoDBNode.js
2 Comments1 Solution155 ViewsLast Modified:
Here's what I understand about "Discriminators..."

http://brucegust.com/adm/bSmart/#review_7

Basically, it's similar to a Foreign Key if I were to try and describe it in the context of a relational database dynamic.

I'm getting an error when I insert a document into a collection that reads like this:

error":true,"msg":"Discriminator with name \"call\" already exists","data":[]}

From what I understand, a "Discriminator" needs to be unique, but I'm not sure I understand that concept let alone how it's going to work in this particular scenario.

I'm trying to add a note. A note is a type of activity. "activity" is related to "company" and I'm going to post the relevant models and code below.

How to fix the error?

Company Schema

const mongoose = require('mongoose');

const { Schema } = mongoose;

const ActivitySchema = new Schema(
  {
    type: {
      type: String,
      required: true,
      enum: [
        'call',
        'visit',
        'reminder',
        'event',
        'Company created',
        'Company updated',
        'Company deleted',
        'Broker info added',
        'Broker info updated',
        'Contact added',
        'Contact updated',
        'Address added',
        'Address updated',
        'Proposal created',
        'Proposal updated',
        'Converted to company',
        'Target selected',
        'Target ignored',
        'Owner change',
        'Proposal assigned',
        'File attached',
        'File removed',
        'User added',
        'User deleted',
        'Service line added',
        'Service line deleted',
        'Swimlane added',
        'Swimlane removed',
        'Custom Fields Updated'
      ]
    },
    company: {
      type: Schema.Types.ObjectId,
      required: true,
      ref: 'Company'
    },
    proposal: {
      type: Schema.Types.ObjectId,
      ref: 'Proposal'
    },
    pipeline: {
      type: Schema.Types.ObjectId, // for tracking activity on each pipeline stage.
      ref: 'PipelineColumn'
    },
    user: {
      type: Schema.Types.ObjectId,
      ref: 'User'
    },
    date: {
      type: Date,
      default: Date.now
    },
    meta: {
      type: Schema.Types.Mixed
    },
    account: {
      type: Schema.Types.ObjectId,
      default: () => global._user.account._id
    },
    active: { type: Boolean, default: true }
  },
  { discriminatorKey: 'type', collation: 'activities' }
);
// removed strict:false and added discriminatorKey for meta.start, meta.end, meta.date fields for type=event/call/note/reminder

module.exports = ActivitySchema;

Company Schema

const mongoose = require('mongoose');

require('../lib/mongoose-types/Email');
require('../lib/mongoose-types/PostalCode');
const mongooseTypePhone = require('mongoose-type-phone');

const { Schema, SchemaTypes } = mongoose;

const CompanySchema = new Schema({
  name: {
    type: String,
    required: true
  },
  status: {
    type: String,
    enum: ['info', 'warning', 'danger', 'success']
  },
  market: {
    type: Schema.Types.ObjectId,
    ref: 'Prospect'
  },
  account: {
    type: Schema.Types.ObjectId,
    default: () => global._user.account._id
  },
  owner: {
    type: Schema.Types.ObjectId,
    ref: 'User'
  },
  files: [
    {
      key: {
        type: String,
        required: true
      },
      name: {
        type: String,
        required: true
      },
      url: {
        type: String,
        required: true
      },
      date: {
        type: Date,
        required: true,
        default: Date.now
      }
    }
  ],
  meta: {
    fundingStatus: {
      type: String,
      required: true,
      enum: ['Unknown', 'Fully', 'Mixed', 'Self'],
      default: 'Unknown'
    },
    ftes: {
      type: Number,
      default: 0
    },
    premium: {
      type: Number,
      default: 0
    },
    revenue: {
      type: Number,
      default: 0
    },
    fteGrouping: {
      type: String,
      enum: [
        'Unknown',
        '50-99',
        '100-499',
        '500-999',
        '1000-4999',
        '> or = 5000'
      ],
      default: 'Unknown'
    },
    industry: String,
    premiumPerFte: {
      type: Number,
      default: 0
    },
    brokerCommissionTotal: {
      type: Number,
      default: 0
    },
    brokerFeesTotal: {
      type: Number,
      default: 0
    },
    ein: {
      type: String
    },
    fax: {
      type: SchemaTypes.Phone,
      defaultRegion: 'US',
      phoneNumberFormat: mongooseTypePhone.PhoneNumberFormat.INTERNATIONAL,
      required: false,
      allowBlank: true
    },
    companyUrl: {
      type: String
    },
    businessDescription: {
      type: String
    },
    SIC: {
      type: String
    },
    SICDescription: {
      type: String
    },
    source: {
      type: String
    }
  },
  brokers: [
    {
      _id: false,
      name: {
        type: String
      },
      normalized: {
        type: String
      },
      street: {
        type: String
      },
      street2: {
        type: String
      },
      city: {
        type: String
      },
      state: {
        type: String,
        match: /^[A-Z]{2}/,
        uppercase: true
      },
      postal: {
        type: SchemaTypes.PostalCode,
        required: false,
        allowBlank: true
      },
      commission: {
        type: Number,
        default: 0
      },
      fees: {
        type: Number,
        default: 0
      },
      feesText: {
        type: String
      }
    }
  ],
  dates: {
    created: {
      type: Date,
      default: Date.now
    },
    updated: Date,
    assigned: Date
  },
  clientMessage: {
    type: String
  },
  active: {
    type: Boolean,
    required: true,
    default: true
  },
  addresses: [
    {
      street: {
        type: String,
        required: true
      },
      street2: {
        type: String,
        required: false
      },
      city: {
        type: String,
        required: true
      },
      state: {
        type: String,
        required: true,
        trim: true,
        uppercase: true,
        match: /^[A-Z]{2}/
      },
      postal: {
        type: SchemaTypes.PostalCode,
        required: true
      },
      county: String,
      country: {
        type: String,
        default: 'US',
        uppercase: true,
        match: /^[A-Z]{2}/
      },
      hash: String,
      formatted: String,
      nonGeo: String,
      default: {
        type: Boolean,
        default: false
      },
      active: {
        type: Boolean,
        default: true
      },
      error_message: String
    }
  ],
  contacts: [
    {
      name: {
        type: String,
        required: true
      },
      email: {
        type: SchemaTypes.Email,
        required: false
      },
      phone: {
        type: SchemaTypes.Phone,
        defaultRegion: 'US',
        phoneNumberFormat: mongooseTypePhone.PhoneNumberFormat.INTERNATIONAL,
        required: false
      },
      default: {
        type: Boolean,
        default: false
      },
      active: {
        type: Boolean,
        default: true
      }
    }
  ],
  deleted: Boolean,
  pipeline: {
    type: Schema.Types.ObjectId
  },
  _pipelineDate: {
    type: Date
  },
  source: {
    type: String
  },
  clientImport: {
    type: Schema.Types.ObjectId
  },
  ACK_ID: {
    type: String,
    index: true
  },
  customFields: {
    type: Schema.Types.Mixed
  }
});

module.exports = CompanySchema;

Code I'm using to insert an activity:

router.post(
  '/companies/add-activity',
  celebrate({
    [Segments.BODY]: Joi.object().keys({
      type: Joi.string().required(),
      company: Joi.array().required(),
      activityType: Joi.string().optional()
    })
  }),
  async (req, res) => {
    if (
      typeof req.body.activityType === 'undefined' ||
      req.body.activityType === ''
    ) {
      req.body.activityType = req.body.type;
    } else {
      // set the activity type as the main type - I added this in after a request to
      // combine several types
      req.body.type = req.body.activityType;
    }

    // validate the content based on the type
    try {
      if (Array.isArray(req.body.company)) {
        const ids = [];
        const companies = req.body.company.filter(
          (elem, pos) => req.body.company.indexOf(elem) === pos
        ); // Remove duplicates
        let counter = companies.length;
        for (let i = 0; i < companies.length; i += 1) {
          try {
            counter -= 1;
            const _companyId = companies[i];
            if (_companyId === 'bulk') {
              ids.push('_0');
              if (counter === 0) {
                res.send(Company.success({ id: companies[0] }));
              }
            } else {
              // eslint-disable-next-line no-await-in-loop
              const id = await Company.addActivity(
                _companyId,
                req.body.type,
                req.body
              );
              ids.push(id);
              if (counter === 0) {
                res.send(Company.success({ id: companies[0] }));
              }
            }
          } catch (e) {
            res.send(Company.error(e));
          }
        }
      } else {
        const id = await Company.addActivity(
          req.body.company,
          req.body.type,
          req.body
        );
        res.send(Company.success({ id }));
      }
    } catch (err) {
      res.send(Company.error(err));
    }
  }
);

What do I need to change and why?

Thanks!
Join the community to see this answer!
Join our exclusive community to see this answer & millions of others.
Unlock 1 Answer and 2 Comments.
Join the Community
Learn from the best

Network and collaborate with thousands of CTOs, CISOs, and IT Pros rooting for you and your success.

Andrew Hancock - VMware vExpert
See if this solution works for you by signing up for a 7 day free trial.
Unlock 1 Answer and 2 Comments.
Try for 7 days

”The time we save is the biggest benefit of E-E to our team. What could take multiple guys 2 hours or more each to find is accessed in around 15 minutes on Experts Exchange.

-Mike Kapnisakis, Warner Bros