Loopback 4 validation returns 422 unexepcted, plus how to require only one of three properties - loopbackjs

My application accepts three different types of phone numbers for a model. I'd like to validate the numbers to match a certain pattern only if that number is not empty. I've tried this in declaring the properties, but it causes a 422 because it's trying to apply the validation even when a number isn't present. Is there a way to apply the pattern only when the property is not empty?
#property({
type: 'string',
jsonSchema: {
maxLength: 14,
pattern: "[(][0-9]{3}[)] [0-9]{3}-[0-9]{4}",
},
})
cell_phone: string;
I'd also like to make sure that at least one of them is not empty. It's not clear from the documentation (or I just completely missed it, which has happened) if this is possible outside of checking for at least one of them in the controller. Is there a way to declare this in the model?

I am assuming you are using getModelSchemaRef in your controller which means you could make the property optional by doing something like this
getModelSchemaRef(ModelClass, { optional: ["cell_phone"] })

Alrighty, maybe not the most pretty, but this works.
The phone number properties are defined as so:
type: 'string',
jsonSchema: {
oneOf: [
{pattern: "[(][0-9]{3}[)] [0-9]{3}-[0-9]{4}"},
{maxLength: 0},
],
},
})
home_phone?: string;
#property({
type: 'string',
jsonSchema: {
oneOf: [
{pattern: "[(][0-9]{3}[)] [0-9]{3}-[0-9]{4}"},
{maxLength: 0},
],
},
})
cell_phone: string;
#property({
type: 'string',
oneOf: [
{pattern: "[(][0-9]{3}[)] [0-9]{3}-[0-9]{4}"},
{maxLength: 0},
]
})
other_phone?: string;
Then I just check to make sure the correct phone number is set in the controller, like so:
if (application[primaryPhoneType] === '' || typeof application[primaryPhoneType] === 'undefined') {
throw new HttpErrors.UnprocessableEntity('The primary_phone_type is set to ' + application.primary_phone_type + ' but ' + primaryPhoneType + ' was empty.');
}

Related

How to use single validator class for both resource creation and modification in AdonisJS 5?

Sometimes I may need to use single validator class for both inserting and updating resources, as opposed to this statement. Otherwise I may have duplicate codes, which inherently goes against DRY principle.
Consider the following case:
Say, I have a products resource in my app, and users of my app can create, update and delete products. Assume that the product model looks something like this:
export default class Product extends BaseModel {
#column({ isPrimary: true })
public id: number
#column()
public code: string
#column()
public title: string
#column()
public description: string
#column()
public price: number
}
Certainly the migration will be very close to the following:
export default class ProductsSchema extends BaseSchema {
protected tableName = 'products'
public async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id').primary()
table.string('code').unique().notNullable() // <= pay attention that this field is unique
table.string('title').notNullable()
table.string('description', 25).notNullable()
table.double('price').notNullable()
})
}
public async down() {
this.schema.dropTable(this.tableName)
}
}
Now users will create a new product. So they will be presented a form, and the validation may look something like:
export default class ProductCreateValidator {
constructor(protected ctx: HttpContextContract) {}
public schema = schema.create({
code: schema.string({ trim: true, escape: true }, [
rules.unique({ table: 'products', column: 'code' }),
]), // <= because this field is unique inside the database
title: schema.string({ trim: true, escape: true }, [
rules.alpha({ allow: ['space'] }),
]),
description: schema.string({ trim: true, escape: true }),
price: schema.number(),
})
public cacheKey = this.ctx.routeKey
public messages = {}
}
The fun begins now! If I create separate class for updating products, most the fields will be the same, except code. So I'll have to duplicate the whole class:
export default class ProductUpdateValidator {
constructor(protected ctx: HttpContextContract) {}
public schema = schema.create({
code: schema.string({ trim: true, escape: true }, [
// rules.unique({ table: 'products', column: 'code' }),
]), // <= I cannot apply unique rule here - because I won't be able to update anymore
title: schema.string({ trim: true, escape: true }, [
rules.alpha({ allow: ['space'] }),
]),
description: schema.string({ trim: true, escape: true }),
price: schema.number(),
})
public cacheKey = this.ctx.routeKey
public messages = {}
}
What if I want to add 3 more fields? With this current setup, I'd have to go to these two class files and add those fields in both of them. And I'd have to go to both these files if I want to adjust some validation logic. It'd be much easier to maintain if I'd be able to use single class for both create and update actions; and it'd automatically cancel the uniqueness check if the particular field of the product that users trying to update hasn't been changed. How could that even possible?
It's very easy to achieve. We need to drop one validator class and modify the other one like so:
export default class ProductValidator {
constructor(protected ctx: HttpContextContract) {}
public schema = schema.create({
code: schema.string({ trim: true, escape: true }, [
rules.unique({
table: 'products',
column: 'code',
whereNot: {
id: this.ctx.request.input('id') || 0 // <= or this may come from route params: this.ctx.params.id
}
}),
]),
title: schema.string({ trim: true, escape: true }, [
rules.alpha({ allow: ['space'] }),
]),
description: schema.string({ trim: true, escape: true }),
price: schema.number(),
})
public cacheKey = this.ctx.routeKey
public messages = {}
}
Let's break this code down:
For inserting new product, the this.ctx.request.input('id') will be undefined, so it'll fallback to 0. So it'll perform SELECT code FROM products WHERE code = ? AND NOT id = ? query with ['<whatever_user_types>', 0]. Since id is the primary key for that table and it cannot be 0, the later condition of the query above will always be TRUE. So the validator will only throw error if the code is found (since the later part is already TRUE). Hence our objective is fulfilled.
For updating existing product, you'll certainly have the ID of the product in hand. Because you're fetching the product from the database, you certainly know its ID. Now put it somewhere of your choice (either inside the update form as <input type="hidden" name="id" value="{{ product.id }}"> or as route param /products/:id/update). Since we have the ID this time around, the this.ctx.request.input('id') (or this.ctx.params.id) will be set. So the query will look like SELECT code FROM products WHERE code = ? AND NOT id = ? query with ['<whatever_user_types>', <product_id>]. This time, the later condition of the query will always be FALSE, so it won't complain if the code matches only with the product we're trying to update and not with any other products. Bingo!
So this is how you can avoid code duplication by utilizing single validator for both create and update actions. Let me know down in comments if you have any other questions.

Using breakParent to force a linebreak

I'm working on a prettier plugin and am struggling to understand the breakParent command.
From the command documentation:
Include this anywhere to force all parent groups to break.
The documentation is quite sparse, so maybe I've just completely misunderstood what it's supposed to do, but my expectation would be that it will line break parent groups in exactly the same way they would have if the line exceeded the maximum width.
I would therefore have expected the following commands
{
type: 'group',
contents: {
type: 'concat',
parts: [
'<div>',
{
type: 'indent',
contents: {
type: 'concat',
parts: [{ type: 'line', soft: true }, 'Text', { type: 'break-parent' }],
},
},
{ type: 'line', soft: true },
'</div>',
],
},
}
to print
<div>
Text
</div>
but instead I get
<div>Text</div>
How do I achieve what I thought breakParent would do, i.e. force the surrounding code to line break from inside the group?
And if that's not possible, what does breakParent actually do?
(Obviously, it can be done by turning the soft lines into hard lines, but that's not the solution I'm looking for because only the code generating "Text" in the example above knows if a line break is needed or not)

How to disable checkbox in certain column Ember js?

May I know how can I disable the rest of the checkbox except for step 2,3 and 4? Those checkbox are link from the checkbox component. And i link the checkbox component into route at columns propertyName:"active". Below is a part of the route code.
export default Route.extend({
model() {
let results = {
workflow: {
columns: [
{
"propertyName": "step",
"title": "Step",
editable: false
},
{
"propertyName": "workflowName",
"title": "Workflow Name",
},
{
"propertyName": "preferredName",
"title": "Your Company Preferred Name",
},
{
"propertyName": "active",
"title": "Active",
component: "adk-workflow-select-row",
editable: false
},
{
title: "Edit",
component: "edit-row",
editable: false
}],
rows: [
{
step: '0',
workflowName: 'New',
preferredName: '新',
},
{
step: '1',
workflowName: 'Budget Approval',
preferredName: '预算批准',
},
{
step: '2',
workflowName: 'Creative',
preferredName: '创作的',
},
{
step: '3',
workflowName: 'Visualize',
preferredName: '想象',
},
{
step: '4',
workflowName: 'Implementation',
preferredName: '履行',
},
{
step: '5',
workflowName: 'In Play',
preferredName: '活性',
},
{
step: '6',
workflowName: 'Completed',
preferredName: '已完成',
},
{
step: '7',
workflowName: 'Canceled',
preferredName: '取消',
},
]
},
This is the adk-workflow-select-row which is the checkbox component. The code below is how i render the checkbox at. This enable all the checkbox. But i only need step 2,3 and 4 checkbox to be enable.
{{#paper-checkbox value=isSelected onChange=(action "clickOnRow" index record)}}{{/paper-checkbox}}
Your questions is a bit hard to answer because you dont show the relevant template code.
Generally you somehow call your your checkbox in your template, and you can just wrap this in an {{#if. Your code is very generic, but I just guess this could be in your edit-row component. So like this:
{{#if some condition}}
{{#paper-checkbox value=isSelected onChange=(action "clickOnRow" index record)}}{{/paper-checkbox}}
{{/if}}
Now the important question is what condition to use. And this kind of depends on what exactly you want. How do you want to configure it? Do you want to keep a global array somehow saying which steps have the checkbox in your rows like this?
{
step: '2',
workflowName: 'Creative',
preferredName: '创作的',
showCheckbox: true,
},
Depending on this what you want could be something like this:
{{#if record.showCheckbox}}
{{#paper-checkbox value=isSelected onChange=(action "clickOnRow" index record)}}{{/paper-checkbox}}
{{/if}}
generally if you're new to ember I can just strongly recommend you to first try to learn how things work in a less generic situation. A generic solution like you have can be awesome, but soon become very complex.

Loopback4 Model Definition options for mongodb collection name

I am using loopback 4 and trying to configure the Model annotation with properties to configure how the collection is created in Mongo.
I have a Model called say Client and I want the collection in Mongo to be called Clients. The cross over with documentation is confusing, as they reference the properties from v3 in v4 docs.
I have tried this:
import {Entity, model, property} from '#loopback/repository';
#model({
settings: {strict: false},
name: 'client',
plural: 'clients',
options: {
mongodb: {
collection: 'clients',
},
},
})
export class Client extends Entity {
#property({
type: 'string',
id: true,
defaultFn: 'uuidv4',
index: true,
})
id: string;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'string',
})
code?: string;
constructor(data?: Partial<Client>) {
super(data);
}
}
With no Joy, still creates the collection as the Class name Client
This is from 2014, but perhaps it still works. Try not putting the mongodb key options
settings: {strict: false},
name: 'client',
plural: 'clients',
mongodb: {
collection: 'clients',
},
Please note that all model settings must be nested inside settings property, LB4 does not support top-level settings yet.
Also the option plural is not used by LB4 as far as I know.
I think the following code should work for you:
#model({
name: 'client',
settings: {
strict: false
mongodb: {
collection: 'clients',
},
},
})
export class Client extends Entity {
// ...
}
UPDATE: I opened a GitHub issue to discuss how to make #model decorator easier to use for users coming from LB3. See https://github.com/strongloop/loopback-next/issues/2142

Ember observe nested array property

I want to observe a nested property. I have the following Ember Array:
specifications: [
{
title: "specification name",
filters: [
{
title: "example"
checked: false
},
{
title: "example 2",
checked: true
}
]
},
...
]
I want to create a computed property:
activeFilters: (->
Ember.computed.filterBy('specifications.#each.filters', 'checked', true),
).property('specifications.#each.filters')
The active filters property is not working, I tried several combinations of property methods, but none worked.
Is the property I am trying to create actually possible?
Yes, it is possible. The thing is that for it to work you can't use a JavaScript array, you need an Ember.ArrayProxy and then you can access its special properties: http://emberjs.com/api/classes/Ember.ArrayProxy.html
specifications: Ember.ArrayProxy.create({
content: [
{
title: "specification name",
},
]
});
So, you'll have something like:
oneSpecChangedCheck: function() {
// don't know which, but someone changed
var s = this.get('specs');
// loop and process
return s;
}.property('specs.#each.checked'),
http://emberjs.jsbin.com/zohuzo/2/edit?html,js,console,output