How to don't validate fields that don't exists in Schema object - joi

I have a schema with two fields, but in some cases is necessary send other field that not is necessary validate. How to allow this fields?
const schemaInsercao = Joi.object({
nome: Joi.string()
.min(3)
.max(255)
.required()
,
ativo: Joi.string()
.max(1)
.required()
.valid('S', 'N')
,
descricao: Joi.string()
.max(255)
.required()
});
....
sending
{
"nome":"Invictos Tecnologia",
"ativo":"S",
"teste":""
}
the error
{
"msg": "\"teste\" is not allowed"
}

https://joi.dev/api/?v=17.6.0#anyvalidatevalue-options
schema.validate(data, {
allowUnknown: true
})

Related

How do Functions work in a Mongoose Schema?

I have a schema where I'm trying to generate a nanoid to show on the front end. (The default MongoDB ObjectID is too long to display to users.)
What I've done is to insert it into my schema as a default value that generates a new string each time an instance of the model is created.
These are the instructions provided by the nanoid docs for dealing with Mongoose
const mySchema = new Schema({
_id: {
type: String,
default: () => nanoid()
}
})
I've replicated this in my code:
const MySchema = new Schema(
{
// a bunch of other properties
nano_id: {
type: String,
default: () => nanoid()
}
},
{ autoIndex: true },
)
The problem I've run into is that for whatever reason. When I use my UI to create a new model with this new code on the back end, after hitting the submit button, the state changes to loading and gets stuck there.
I found a little hack where I can save my code in VSCode which forces a hot reload. This breaks out of the loading stage where I can then resubmit and it works as expected - nanoid is generated, displayed every where I want it, etc.
When my new nano_id property is removed from the Schema, I don't have to do this reloading hack to get it to save. So I know the bug has something to do with this.
What is actually happening with the syntax default: () => nanoid()?
My hypothesis is that there is some promise that is being generated or something that isn't resolving properly. Can't seem to find anything about this in the docs though. Other than related specifically to Date and Date.now, which I'm already using and is working fine.
Edit: adding function code
// at top of file where Schema is defined
const { customAlphabet } = require('nanoid');
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const nanoid = customAlphabet(alphabet, 8);
Edit: adding request handler
router.post("/create", async (req, res) => {
const payload = req.body
// Map request payload to Referral Model
const new_referral = new Referral({
referral_status: payload.referral_status,
referral_agency: payload.referral_agency,
date_assigned: payload.date_assigned,
referral_source: payload.referral_source,
referral_source_email: payload.referral_source_email,
referral_source_name: payload.referral_source_name,
referral_title: payload.referral_title,
referral_source_phone: payload.referral_source_phone,
referral_need: payload.referral_need || [],
additional_details: payload.additional_details,
dob: payload.dob,
last_name: payload.last_name,
first_name: payload.first_name,
middle_name: payload.middle_name,
gender: payload.gender,
language: payload.language,
address_1: payload.address_1,
address_2: payload.address_2,
city: payload.city,
state: payload.state,
county: payload.county,
zip: payload.zip,
mailing_address_1: payload.mailing_address_1,
mailing_address_2: payload.mailing_address_2,
mailing_city: payload.mailing_city,
mailing_state: payload.mailing_state,
mailing_zip: payload.mailing_zip,
phone: payload.phone,
primary_phone_valid: payload.primary_phone_valid,
phone_secondary: payload.phone_secondary,
secondary_phone_valid: payload.secondary_phone_valid,
leave_message: payload.leave_message,
insurance_1: payload.insurance_1,
insurance_1_plan: payload.insurance_1_plan,
insurance_1_group_number: payload.insurance_1_group_number,
insurance_1_policy_number: payload.insurance_1_policy_number,
insurance_2: payload.insurance_2,
insurance_2_plan: payload.insurance_2_plan,
insurance_2_group_number: payload.insurance_2_group_number,
insurance_2_policy_number: payload.insurance_2_policy_number,
insurance_3: payload.insurance_3,
insurance_3_plan: payload.insurance_3_plan,
insurance_3_group_number: payload.insurance_3_group_number,
insurance_3_policy_number: payload.insurance_3_policy_number,
appointment_availability: payload.appointment_availability,
preferred_times: payload.preferred_times,
appointment_date: payload.appointment_date,
appointment_time: payload.appointment_time,
assessment_harp: payload.assessment_harp,
assessment_contact_date: payload.assessment_contact_date,
assessment_date_completed: payload.assessment_date_completed,
assessment_date_submitted: payload.assessment_date_submitted,
assessment_date_approved: payload.assessment_date_approved,
cin: payload.cin,
number_cancelled: payload.number_cancelled,
number_noshows: payload.number_noshows,
date_received: payload.date_received,
history: payload.history,
consent: payload.consent,
consent_date: payload.date_received,
project: payload.project || null,
contact_needs: payload.contact_needs || [],
contact_type: payload.contact_type || [],
notes: payload.notes,
contact_details: payload.contact_details,
contact_date: payload.contact_date,
contact_time: payload.contact_time,
number_of_contacts: payload.number_of_contacts,
address_type: payload.address_type,
shelter_name: payload.shelter_name,
address_valid: payload.address_valid,
agency_therapist: payload.agency_therapist,
phone_agency_therapist: payload.phone_agency_therapist,
email_agency_therapist: payload.email_agency_therapist,
agency_prescribing_clinician: payload.agency_prescribing_clinician,
phone_agency_prescribing_clinician:
payload.phone_agency_prescribing_clinician,
email_agency_prescribing_clinician:
payload.email_agency_prescribing_clinician,
primary_care_provider: payload.primary_care_provider,
phone_primary_care_provider: payload.phone_primary_care_provider,
email_primary_care_provider: payload.email_primary_care_provider,
email_client: payload.email_client,
referral_encounter_note: payload.referral_encounter_note,
health_home_enrolled: payload.health_home_enrolled,
health_home_facility: payload.health_home_facility,
health_home_enrollment_date: payload.health_home_enrollment_date,
care_management_enrolled: payload.care_management_enrolled,
care_management_facility: payload.care_management_facility,
care_management_enrollment_date: payload.care_management_enrollment_date,
last_annual_physical_date: payload.last_annual_physical_date,
last_pcp_followup_date: payload.last_pcp_followup_date,
last_annual_physical_time: payload.last_annual_physical_time,
last_pcp_followup_time: payload.last_pcp_followup_time,
insurance_1_effective_date: payload.insurance_1_effective_date,
insurance_1_expiration_date: payload.insurance_1_expiration_date,
insurance_2_effective_date: payload.insurance_2_effective_date,
insurance_2_expiration_date: payload.insurance_2_expiration_date,
insurance_3_effective_date: payload.insurance_3_effective_date,
insurance_3_expiration_date: payload.insurance_3_expiration_date,
qm_1_checkbox: payload.qm_1_checkbox,
qm_1_date: payload.qm_1_date,
qm_2_checkbox: payload.qm_2_checkbox,
qm_2_date: payload.qm_2_date,
qm_3_checkbox: payload.qm_3_checkbox,
qm_3_date: payload.qm_3_date,
qm_4_checkbox: payload.qm_4_checkbox,
qm_4_date: payload.qm_4_date,
qm_5_checkbox: payload.qm_5_checkbox,
qm_5_date: payload.qm_5_date,
qm_6_checkbox: payload.qm_6_checkbox,
qm_6_date: payload.qm_6_date,
is_guardian: payload.is_guardian,
guardian_first_name: payload.guardian_first_name,
guardian_last_name: payload.guardian_last_name,
engagement_contact_type: payload.engagement_contact_type,
engagement_date: payload.engagement_date,
engagement_time: payload.engagement_time,
engagement_number_of_contacts: payload.engagement_number_of_contacts,
engagement_contact_details: payload.engagement_contact_details,
active_duty: payload.active_duty,
veteran: payload.veteran,
programs: payload.programs,
outcomes: payload.outcomes,
user_access: payload.user_access,
ethnicity: payload.ethnicity,
quality_metrics: payload.quality_metrics,
hospital_site_address: payload.hospital_site_address,
hospital_site_name: payload.hospital_site_name,
hospital_site_npi: payload.hospital_site_npi,
hospital_site_phone: payload.hospital_site_phone,
pcp_site_name: payload.pcp_site_name,
pcp_name: payload.pcp_name,
pcp_npi: payload.pcp_npi,
pcp_phone: payload.pcp_phone,
pcp_address: payload.pcp_address,
care_management: {
contact_needs: payload.care_management_contact_needs,
contact_type: payload.care_management_contact_type,
number_of_contacts: payload.care_management_number_of_contacts,
appointment_date: payload.care_management_appointment_date,
appointment_time: payload.care_management_appointment_time,
contact_date: payload.care_management_contact_date,
contact_time: payload.care_management_contact_time,
contact_details: payload.care_management_contact_details,
contact_duration: payload.care_management_contact_duration,
number_of_contacts: payload.care_management_number_of_contacts,
},
peer_services: {
outcome_of_intervention: payload.peer_services_outcome_of_intervention,
contact_needs: payload.peer_services_contact_needs,
contact_type: payload.peer_services_contact_type,
number_of_contacts: payload.peer_services_number_of_contacts,
appointment_date: payload.peer_services_appointment_date,
appointment_time: payload.peer_services_appointment_time,
contact_date: payload.peer_services_contact_date,
contact_time: payload.peer_services_contact_time,
contact_details: payload.peer_services_contact_details,
contact_duration: payload.peer_services_contact_duration,
number_of_contacts: payload.peer_services_number_of_contacts,
},
physical_health: {
contact_needs: payload.physical_health_contact_needs,
contact_type: payload.physical_health_contact_type,
number_of_contacts: payload.physical_health_number_of_contacts,
contact_date: payload.physical_health_contact_date,
contact_time: payload.physical_health_contact_time,
contact_details: payload.physical_health_contact_details,
contact_duration: payload.physical_health_contact_duration,
number_of_contacts: payload.physical_health_number_of_contacts,
hospital_site_address: payload.physical_health_hospital_site_address,
hospital_site_name: payload.physical_health_hospital_site_name,
hospital_site_npi: payload.physical_health_hospital_site_npi,
hospital_site_phone: payload.physical_health_hospital_site_phone,
pcp_site_name: payload.physical_health_pcp_site_name,
pcp_name: payload.physical_health_pcp_name,
pcp_npi: payload.physical_health_pcp_npi,
pcp_phone: payload.physical_health_pcp_phone,
pcp_address: payload.physical_health_pcp_address,
pcp_appointment_date: payload.physical_health_pcp_appointment_date,
pcp_appointment_time: payload.physical_health_pcp_appointment_time,
last_pcp_followup_date: payload.physical_health_last_pcp_followup_date,
last_pcp_followup_time: payload.physical_health_last_pcp_followup_time,
last_annual_physical_date:
payload.physical_health_last_annual_physical_date,
last_annual_physical_time:
payload.physical_health_last_annual_physical_time,
breast_cancer_screening: payload.breast_cancer_screening,
breast_cancer_screening_date: payload.breast_cancer_screening_date,
well_child_visit_3_18: payload.well_child_visit_3_18,
well_child_visit_3_18_date: payload.well_child_visit_3_18_date,
well_child_first_30: payload.well_child_first_30,
well_child_first_30_date: payload.well_child_first_30_date,
seven_day_hospital_follow_up: payload.seven_day_hospital_follow_up,
seven_day_hospital_follow_up_date:
payload.seven_day_hospital_follow_up_date,
thirty_day_hospital_follow_up: payload.thirty_day_hospital_follow_up,
thirty_day_hospital_follow_up_date:
payload.thirty_day_hospital_follow_up_date,
emergency_dept_follow_up: payload.emergency_dept_follow_up,
emergency_dept_follow_up_date: payload.emergency_dept_follow_up_date,
diabetes_screening_schizo_bipolar:
payload.diabetes_screening_schizo_bipolar,
diabetes_screening_schizo_bipolar_date:
payload.diabetes_screening_schizo_bipolar_date,
potential_avoidable_ed_utilization:
payload.potential_avoidable_ed_utilization,
potential_avoidable_ed_utilization_date:
payload.potential_avoidable_ed_utilization_date,
cervical_cancer_screening: payload.cervical_cancer_screening,
cervical_cancer_screening_date: payload.cervical_cancer_screening_date,
csc_eye: payload.csc_eye,
csc_eye_date: payload.csc_eye_date,
amr: payload.amr,
amr_date: payload.amr_date,
col: payload.col,
col_date: payload.col_date,
},
outpatient_mental_health: {
contact_needs: payload.outpatient_mental_contact_needs,
contact_type: payload.outpatient_mental_contact_type,
number_of_contacts: payload.outpatient_mental_number_of_contacts,
appointment_date: payload.outpatient_mental_appointment_date,
appointment_time: payload.outpatient_mental_appointment_time,
contact_date: payload.outpatient_mental_contact_date,
contact_time: payload.outpatient_mental_contact_time,
contact_details: payload.outpatient_mental_contact_details,
contact_duration: payload.outpatient_mental_contact_duration,
number_of_contacts: payload.outpatient_mental_number_of_contacts,
type_of_appointment: payload.outpatient_mental_type_of_appointment,
diagnosis: payload.outpatient_mental_diagnosis,
grpa: payload.outpatient_mental_grpa,
grpa_date_completed: payload.outpatient_mental_grpa_date_completed,
grpa_frequency: payload.outpatient_mental_grpa_frequency,
noms: payload.outpatient_mental_noms,
noms_date_completed: payload.outpatient_mental_noms_date_completed,
noms_frequency: payload.outpatient_mental_noms_frequency,
},
outpatient_substance_use: {
contact_needs: payload.outpatient_substance_contact_needs,
contact_type: payload.outpatient_substance_contact_type,
number_of_contacts: payload.outpatient_substance_number_of_contacts,
appointment_date: payload.outpatient_substance_appointment_date,
appointment_time: payload.outpatient_substance_appointment_time,
contact_date: payload.outpatient_substance_contact_date,
contact_time: payload.outpatient_substance_contact_time,
contact_details: payload.outpatient_substance_contact_details,
contact_duration: payload.outpatient_substance_contact_duration,
number_of_contacts: payload.outpatient_substance_number_of_contacts,
type_of_appointment: payload.outpatient_substance_type_of_appointment,
diagnosis: payload.outpatient_substance_diagnosis,
grpa: payload.outpatient_substance_grpa,
grpa_date_completed: payload.outpatient_substance_grpa_date_completed,
grpa_frequency: payload.outpatient_mental_grpa_frequency,
noms: payload.outpatient_substance_noms,
noms_date_completed: payload.outpatient_substance_noms_date_completed,
noms_frequency: payload.outpatient_mental_noms_frequency,
},
})
if (!payload.duplicate_skip) {
// Check if there is already a referral with same first name, last name and dob. If so return an error
await Referral.find(
{
$and: [
{
last_name: {
$regex: new RegExp(`^${payload.last_name}$`, "i"),
},
first_name: {
$regex: new RegExp(`^${payload.first_name}$`, "i"),
},
dob: {
$regex: new RegExp(`^${payload.dob}$`, "i"),
},
project: {
$regex: new RegExp(`^${payload.project}$`, "i"),
},
},
],
},
(err, response) => {
if (response.length !== 0) {
var message = {
message:
"Referral with the same First Name, Last Name and Date of Birth already exists.",
type: "duplicate",
}
duplicate = true
return res.status(400).json(message)
}
},
)
} else {
new_referral
.save()
.then((referral) => {
// If an agency adds a referral with 'Agency' field blank / None, send CBHS email that new referral was added
if (
referral.referral_agency === "None" ||
referral.referral_agency === "CBHS" ||
referral.referral_agency === ""
) {
const send_to = "CBHS"
updateAgencyNotification(send_to, referral.project[0])
}
return res.status(200).json(referral)
})
.catch((err) => {
// Validation error, missing keys on required fields for referral creation
logger.error(req.originalUrl + err)
if (err.name === "ValidationError") {
missing_keys = []
for (err in err.errors) {
switch (err) {
case "referral_source":
missing_keys.push("Referral Source")
break
case "referral_source_email":
missing_keys.push("Referral Source Email")
break
case "referral_need":
missing_keys.push("Referral Need")
break
case "last_name":
missing_keys.push("Last Name")
break
case "first_name":
missing_keys.push("First Name")
break
case "middle_name":
missing_keys.push("Middle Name")
break
case "gender":
missing_keys.push("Gender")
break
case "address_1":
missing_keys.push("Address 1")
break
case "county":
missing_keys.push("County")
break
case "zip":
missing_keys.push("Zipcode")
break
case "phone":
missing_keys.push("Phone Number")
break
}
}
const validationError = {
message: "Validation Error",
fields: missing_keys,
}
return res.status(400).json(validationError)
}
})
}
})
problem here was there was no logic to save when duplicate skip was false.
if (!payload.duplicate_skip)
so it looked for something with the same first, last and dob,
await Referral.find(
{
$and: [
{
last_name: {
$regex: new RegExp(`^${payload.last_name}$`, "i"),
},
first_name: {
$regex: new RegExp(`^${payload.first_name}$`, "i"),
},
dob: {
$regex: new RegExp(`^${payload.dob}$`, "i"),
},
project: {
$regex: new RegExp(`^${payload.project}$`, "i"),
},
},
],
}
and if there was no match, it just did nothing.
the logic to save
else {
new_referral
.save()
.then((referral) => {
// If an agency adds a referral with 'Agency' field blank / None, send CBHS email that new referral was added
if (
referral.referral_agency === "None" ||
referral.referral_agency === "CBHS" ||
referral.referral_agency === ""
) {
const send_to = "CBHS"
updateAgencyNotification(send_to, referral.project[0])
}
return res.status(200).json(referral)
})
.catch((err) => {
// Validation error, missing keys on required fields for referral creation
logger.error(req.originalUrl + err)
if (err.name === "ValidationError") {
missing_keys = []
for (err in err.errors) {
switch (err) {
case "referral_source":
missing_keys.push("Referral Source")
break
case "referral_source_email":
missing_keys.push("Referral Source Email")
break
case "referral_need":
missing_keys.push("Referral Need")
break
case "last_name":
missing_keys.push("Last Name")
break
case "first_name":
missing_keys.push("First Name")
break
case "middle_name":
missing_keys.push("Middle Name")
break
case "gender":
missing_keys.push("Gender")
break
case "address_1":
missing_keys.push("Address 1")
break
case "county":
missing_keys.push("County")
break
case "zip":
missing_keys.push("Zipcode")
break
case "phone":
missing_keys.push("Phone Number")
break
}
}
const validationError = {
message: "Validation Error",
fields: missing_keys,
}
return res.status(400).json(validationError)
}
})
}
was never triggered when skip was false
so i added it after
if (response.length !== 0) {
var message = {
message:
"Referral with the same First Name, Last Name and Date of Birth already exists.",
type: "duplicate",
}
duplicate = true
return res.status(400).json(message)
}
meaning, "after checking for a duplicate (skip is false), if you don't get anything back (response === 0), then go ahead and actually create and save a new instance of the model"

JOI validation when condition not working as expected

I have the following JOI schema defined;
schema = Joi.object({
flag: Joi.boolean()
.required(),
toolDetail: Joi.array().items(
Joi.object({
amount: Joi.number()
.min(0)
.max(499.99)
.precision(2)
.options({ convert: false })
.when('flag', {
is: true,
then: Joi.number().required(),
otherwise: Joi.number()
.strip()
.optional()
.allow(''),
}),
tool: Joi.string()
.min(0)
.max(500)
.when('flag', {
is: true,
then: Joi.string().required(),
otherwise: Joi.string()
.strip()
.optional()
.allow(''),
}),
}),
),
});
I have confirmed that the flag is being set correctly, but it seems that 'amount' and 'tool' is always hitting the otherwise condition, regardless of the value set in the 'flag'.
Is there something incorrect in my definition?
Works as expected once I added 4 dots prefix to the flag
.when('....flag', {
https://joi.dev/api/?v=17.2.1#relative-references

How to validate for ObjectID

Using Joi schema validation, is it possible to validate against MongoDB ObjectID's?
Something like this could be great:
_id: Joi.ObjectId().required().error(errorParser),
const Joi = require('#hapi/joi')
Joi.objectId = require('joi-objectid')(Joi)
const schema = Joi.object({
id: Joi.objectId(),
name: Joi.string().max(100),
date: Joi.date()
})
checkout https://www.npmjs.com/package/joi-objectid
I find that if I do
Joi.object({
id: Joi.string().hex().length(24)
})
it works without installing any external library or using RegEx
The hex makes sure the string contains only hexadecimal characters and the length makes sure that it is a string of exactly 24 characters
This package works if you are using the new version of Joi.
const Joi = require('joi-oid')
const schema = Joi.object({
id: Joi.objectId(),
name: Joi.string(),
age: Joi.number().min(18),
})
package: https://www.npmjs.com/package/joi-oid
If you want a TypeScript version of the above library integrated with Express without installing anything:
import Joi from '#hapi/joi';
import { createValidator } from 'express-joi-validation';
export const JoiObjectId = (message = 'valid id') => Joi.string().regex(/^[0-9a-fA-F]{24}$/, message)
const validator = createValidator({
passError: true,
});
const params = Joi.object({
id: JoiObjectId().required(),
});
router.get<{ id: string }>(
'/:id',
validator.params(params),
(req, res, next) => {
const { id } = req.params; // id has string type
....
}
);
I share mine
let id =
Joi.string()
.regex(/^[0-9a-fA-F]{24}$/)
.message('must be an oid')
With joi naked package you can use this:
const ObjectId = joi.object({
id: joi.binary().length(12),
})
This is a core function without any external package. I have used Joi and mongoose packages to achieve it.
const Joi = require("joi");
const ObjectID = require("mongodb").ObjectID;
const schema = Joi.object({
id: Joi.string().custom((value, helpers) => {
const filtered = ObjectID.isValid(value)
return !filtered ? helpers.error("any.invalid") : value;
},
"invalid objectId", ).required(),
name: Joi.string(),
age: Joi.number().min(18),
})
I see many correct answers here, but I also want to express my opinion.
I am not against installing npm packages, but installing one package just to validate an object id is overkill. I know programmers are lazy but C'mooon :)
Here is my full code function that validates two object id properties, very simple:
function validateRental(rental) {
const schema = Joi.object({
movieId: Joi.string()
.required()
.regex(/^[0-9a-fA-F]{24}$/, 'object Id'),
customerId: Joi.string()
.required()
.regex(/^[0-9a-fA-F]{24}$/, 'object Id'),
})
const { error } = schema.validate(rental)
return {
valid: error == null,
message: error ? error.details[0].message : null,
}
}
This way, if any of the properties contain invalid id like this:
{
"movieId": "123456",
"customerId": "63edf556f383d108d54a68a0"
}
This will be the error message:
`"movieId" with value "123456" fails to match the object Id pattern`

Meteor - node simple schema validate data to match schema

I want to change my Rest-API validation to node simple schema for schema definition and collection2#core for schema validation.
I want to use the Person schema to validate the data provided by the users.
Schemas = {};
Schemas.Person = new SimpleSchema({
name: {
type: String,
label: "Person's Name",
unique: true,
max: 200
},
surname: {
type: String,
unique: true,
label: "person's surname"
},
};
validData = API.utility.validate(data, Schemas.Person });
API: {
utility: {
validate: function(data, schema) {
return "The SimpleSchema Validation";
}
}
};
This case is described in the simpl-schema documentation
With your schema definition you can just do:
Schemas.person.validate(data);
If right after that you want to look at the result or the errors:
Schemas.person.isValid();
Schemas.person.validationErrors();

Sequelize.js - How to create non-trivial associations without raw SQL?

Here is my situation:
I'm using postgres 9.4, Sequelize ORM and have following models:
Service
serviceCode - primary key, string of 6 characters
serviceTitle - string
ServiceGroup
serviceCodePrefixes - array of strings that are prefixes for Service.serviceCode
serviceGroupTitle - string
Task
serviceCode - reference to Service
I need to build Task object populated with Service and ServiceGroup objects. Example:
In database:
Service {
serviceCode: '123232',
serviceTitle: 'svc title #1',
}
ServiceGroup {
serviceCodePrefix: ['12', '13', '92', ...],
serviceGroupTitle: 'svc grp title #1',
}
Task {
serviceCode: '123232',
}
Result:
Task {
service: {
serviceTitle: 'svc title #1',
},
serviceGroup: {
serviceGroupTitle: 'svc grp title #1',
},
}
The problem is that serviceCodePrefix contains not simple IDs, which can be used to create association using hasOne/belongsTo/etc., but prefix for ID.
So questions is: how this can be done without raw sql?
Turns out that right now Sequelize has experimental feature: 'on' option for 'include'. This option allows users to customize joining conditions. So my problem can be solved this way:
const Service = sequelize.define('service', {
serviceTitle: Sequelize.STRING,
serviceCode: Sequelize.STRING,
});
const ServiceGroup = sequelize.define('service_group', {
serviceGroupTitle: Sequelize.STRING,
// Array of prefixes (e.g. ['01%', '023%'])
serviceCodePrefix: Sequelize.ARRAY(Sequelize.STRING),
});
const Task = sequelize.define('task', {
taskTitle: Sequelize.STRING,
serviceCode: Sequelize.STRING,
});
Task.belongsTo(Service, { foreignKey: 'serviceCode' });
// Hack needed to allow 'include' option to work
Task.hasMany(ServiceGroup, { foreignKey: 'serviceCodePrefix', constraints: false });
// And finally
Task.findAll({
include: [
{ model: Service },
{
model: ServiceGroup,
on: [' "task"."serviceCode" LIKE ANY("serviceGroup"."serviceCodePrefix") '],
},
],
});
Not sure about the performance though.