0

I have a Mongoose abTest document that has two fields:

  1. status. This is a string enum and can be of type active, inactive or draft.
  2. validCountryCodes. This is an array of strings enums (GB, EU, AU etc). By default, it will be empty.

In the DB, at any one time, I only want there to be one active abTest for each validCountryCode so I'm performing some validation prior to creating or editing a new abTest.

To do this, I've written a function that attempts to count the number of documents that have a status of active and that contain one of the countryCodes.

The function will then return if the count is more than one. If so, I will throw a validation error.

if (params.status === 'active') {
  const activeTestForCountryExists = await checkIfActiveAbTestForCountry(
    validCountryCodes,
  );
  if (params.activeTestForCountryExists) {
    throw new ValidationError({
      message: 'There can only be one active test for each country code.',
    });
  }
}

const abTest = await AbTest.create(params);

checkIfActiveAbTestForCountry() looks like this:

const checkIfActiveAbTestForCountry = async countryCodes => {
  const query = {
    status: 'active',
  };

  if (
    !countryCodes ||
    (Array.isArray(countryCodes) && countryCodes.length === 0)
  ) {
    query.validCountryCodes = {
      $eq: [],
    };
  } else {
    query.validCountryCodes = { $in: [countryCodes] };
  }

  const count = await AbTest.countDocuments(query);
  return count > 0;
};

The count query should count not only exact array matches, but for any partial matches.

If in the DB there is an active abTest with a validCountryCodes array of ['GB', 'AU',], the attempting to create a new abTest with ['GB' should fail. As there is already a test with GB as a validCountryCode.

Similarly, if there is a test with a validCountryCodes array of ['AU'], then creating a test with validCountryCodes of ['AU,'NZ'] should also fail.

Neither is enforced right now.

How can I do this? Is this possible write a query that checks for this?

I considered iterating over params.validCountryCodes and counting the docs that include each, but this seems like bad practice.

judgejab
  • 519
  • 1
  • 3
  • 14

2 Answers2

0

take a look at this MongoDB documantation.

As I understood what you need is to find out if there is any document that contains at least one of the specified countryCodes and it has active status. then your query should look like this:

{ 
  status: 'active',
  $or: [
    { validCountryCodes: countryCodes[0] },
    { validCountryCodes: countryCodes[1] },
    // ...
  ]
}

note that counting documents is not an efficient manner to check if a document exists or not, instead use findOne with only one field being projected.

Mosius
  • 1,602
  • 23
  • 32
  • If I do this then a document with a `validCountryCodes` of `['AU','GB']` won't match `['GB']`. It should! – judgejab Sep 16 '20 at 16:25
  • 1
    if you have a document like this: `{status: 'active', validCountryCodes: ['AU','GB']}` it will match this query `{ status: 'active', validCountryCodes: 'GB' }` ` – Mosius Sep 16 '20 at 16:40
  • True! But the argument coming in through `checkIfActiveAbTestForCountry` is an array of `countryCodes`, so I'm essentially looking to count the documents in which there are at least one common element – judgejab Sep 16 '20 at 16:47
  • @judgejab I have updated the answer, you can use a loop to build the `$or` array – Mosius Sep 17 '20 at 05:37
0

You are using the correct mongo-query for your requirement. Can you verify the actual queries executed from your application is the same? Check here

{ status: 'active', validCountryCodes: { $in: [ countryCodes ] } }

For eg; below query :

{ status: 'active', validCountryCodes: { $in: ['GB' ] } }

should match document :

{ status: 'active', validCountryCodes: ['AU','GB'] }
cyrilantony
  • 274
  • 3
  • 14