Skip to content

Commit

Permalink
improvement: allow whereNot contraints for the unique and the exists …
Browse files Browse the repository at this point in the history
…rules

If the value inside key-value pair is an array, then we will apply a whereIn or
whereNotIn clause
  • Loading branch information
thetutlage committed Apr 30, 2020
1 parent 789f038 commit 1bdf5e6
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 66 deletions.
24 changes: 11 additions & 13 deletions adonis-typings/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,17 @@
declare module '@ioc:Adonis/Core/Validator' {
import { Rule } from '@ioc:Adonis/Core/Validator'

export interface Rules {
exists (options: {
table: string,
column: string,
connection?: string,
constraints?: { [key: string]: any } | { [key: string]: any }[],
}): Rule
export type DbRowCheckOptions = {
table: string,
column: string,
connection?: string,
constraints?: { [key: string]: any },
where?: { [key: string]: any },
whereNot?: { [key: string]: any },
}

unique (options: {
table: string,
column: string,
connection?: string,
constraints?: { [key: string]: any } | { [key: string]: any }[],
}): Rule
export interface Rules {
exists (options: DbRowCheckOptions): Rule
unique (options: DbRowCheckOptions): Rule
}
}
103 changes: 84 additions & 19 deletions src/Bindings/Validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,28 @@
import { Exception } from '@poppinss/utils'
import { DatabaseContract } from '@ioc:Adonis/Lucid/Database'
import { DatabaseQueryBuilderContract } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'
import { validator as validatorStatic, ValidationRuntimeOptions } from '@ioc:Adonis/Core/Validator'
import {
DbRowCheckOptions,
ValidationRuntimeOptions,
validator as validatorStatic,
} from '@ioc:Adonis/Core/Validator'

/**
* Shape of constraint after normalization
*/
type NormalizedConstraint = {
key: string,
operator: 'in' | 'eq',
value: string | string[],
}

/**
* Normalized validation options
*/
type NormalizedOptions = Omit<DbRowCheckOptions, 'constraints' | 'where' | 'whereNot'> & {
where: NormalizedConstraint[],
whereNot: NormalizedConstraint[],
}

/**
* Checks for database rows for `exists` and `unique` rule.
Expand All @@ -20,22 +41,64 @@ class DbRowCheck {
}

/**
* Applies user defined constraints on the query builder
* Applies user defined where constraints on the query builder
*/
private applyWhere (query: DatabaseQueryBuilderContract, constraints: NormalizedConstraint[]) {
if (!constraints.length) {
return
}

constraints.forEach(({ key, operator, value }) => {
if (operator === 'in') {
query.whereIn(key, value as string[])
} else {
query.where(key, value)
}
})
}

/**
* Applies user defined where not constraints on the query builder
*/
private applyWhereNot (query: DatabaseQueryBuilderContract, constraints: NormalizedConstraint[]) {
if (!constraints.length) {
return
}

constraints.forEach(({ key, operator, value }) => {
if (operator === 'in') {
query.whereNotIn(key, value as string[])
} else {
query.whereNot(key, value)
}
})
}

/**
* Normalizes constraints
*/
private applyConstraints (query: DatabaseQueryBuilderContract, constraints: any[]) {
if (constraints.length > 1) {
query.where((builder) => {
constraints.forEach((constraint) => builder.orWhere(constraint))
})
} else {
constraints.forEach((constraint) => query.where(constraint))
private normalizeConstraints (constraints: DbRowCheckOptions['where']) {
const normalized: NormalizedConstraint[] = []
if (!constraints) {
return normalized
}

/**
* Normalize object into an array of objects
*/
return Object.keys(constraints).reduce((result, key) => {
const value = constraints[key]
const operator = Array.isArray(value) ? 'in' : 'eq'
result.push({ key, value, operator })

return result
}, normalized)
}

/**
* Compile validation options
*/
public compile (options) {
public compile (options: DbRowCheckOptions) {
/**
* Ensure options are defined with table and column name
*/
Expand All @@ -44,20 +107,21 @@ class DbRowCheck {
}

/**
* Normalize where constraints
* Emit warning
*/
let constraints: { [key: string]: any }[] = []
if (options.constraints && Array.isArray(options.constraints)) {
constraints = options.constraints
} else if (options.constraints && typeof (options.constraints) === 'object' && options.constraints !== null) {
constraints = [options.constraints]
if (options.constraints) {
process.emitWarning(
'DeprecationWarning',
'"options.constraints" have been depreciated. Use "options.where" instead.',
)
}

return {
table: options.table,
column: options.column,
connection: options.connection,
constraints: constraints,
where: this.normalizeConstraints(options.where || options.constraints),
whereNot: this.normalizeConstraints(options.whereNot),
}
}

Expand All @@ -66,11 +130,12 @@ class DbRowCheck {
*/
public async validate (
value: any,
{ table, column, constraints, connection }: any,
{ table, column, where, whereNot, connection }: NormalizedOptions,
{ pointer, errorReporter, arrayExpressionPointer }: ValidationRuntimeOptions,
) {
const query = this.database.connection(connection).query().from(table).where(column, value)
this.applyConstraints(query, constraints)
this.applyWhere(query, where)
this.applyWhereNot(query, whereNot)

const row = await query.first()
if (this.ruleName === 'exists') {
Expand Down
Loading

0 comments on commit 1bdf5e6

Please sign in to comment.