20210917 - findByIdAndUpdate does not run validations
2021-09-17 00:00:00

在创建Schema时应用了Validations,设置了required、minlength、unique等等限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const personSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'The name is required'],
unique: true,
minlength: [3, 'The name has to be at least three characters long']
},
number: {
type: String,
required: [true, 'The phone number is required'],
minlength: [8, 'The phone number must have at least 8 digits']
}
})

personSchema.plugin(uniqueValidator)

在POST请求也就是创建新联系人的时候的确有用(大概是用了save()的缘故?)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
app.post('/api/persons', (req, res, next) => {
const body = req.body
console.log(body)

const person = new Person({
name: body.name,
number: body.number,
})
console.log(person)

person.**save()**
.then(savedPerson => {
console.log(savedPerson)
return savedPerson.toJSON()
})
.then(savedAndFormattedPerson => {
res.json(savedAndFormattedPerson)
})
.catch(error => next(error))
})

但如果要修改(PUT请求)联系人信息,Validations就无法生效了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.put('/api/persons/:id', (req, res, next) => {
const body = req.body
const person = {
name: body.name,
number: body.number,
}

Person.findByIdAndUpdate(req.params.id, person, {new: true})
.then(updatedPerson => {
if (updatedPerson) {
res.json(updatedPerson)
} else {
res.status(404).end()
}
})
.catch(error => next(error))
})

查了Mongoose文档:

runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model’s schema.

Mongoose v6.0.6:

以及相关问答:

Mongoose findByIdAndUpdate not running validations on subdocuments

According to the documentation, validators seems to work only for update() and findOneAndUpdate() if runValidators is set to true.

但其实文档里也说过findByAndUpdate也可以支持有限的validation的。。。(可能这个回答时间比较久远了,现在好像是可行的)

见下:

Note:

findOneAndX and findByIdAndX functions support limited validation that you can enable by setting the runValidators option.

If you need full-fledged validation, use the traditional approach of first retrieving the document.

1
2
3
const doc = await Model.findById(id);
doc.name = 'jason bourne';
await doc.save();

按照文档试了一下好像确实可以

修改后的PUT代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.put('/api/persons/:id', (req, res, next) => {
const body = req.body
const person = {
name: body.name,
number: body.number,
}

Person.findByIdAndUpdate(req.params.id, person, **{new: true, runValidators: true}**)
.then(updatedPerson => {
if (updatedPerson) {
res.json(updatedPerson)
} else {
res.status(404).end()
}
})
.catch(error => next(error))
})

Untitled

前端也作了修改,加了if-else判断error的类型后再输出报错的message,也不知道在异步方法下加判断会不会有坑,但功能上是实现了的。相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
personService
.update(samePerson.id, changedPerson)
.then(returnedPerson => {
console.log(returnedPerson)
setPersons(persons.map(person => person.id === samePerson.id ? returnedPerson : person))
setNewName('')
setNewNumber('')
setError(false)
setMessage(
`Updated ${returnedPerson.name}'s number`
)
setTimeout(() => {
setMessage(null)
}, 5000)
})
.catch(error => {
console.log(error.response.data.error)
**if (error.response.data.error) {
setMessage(
`${error.response.data.error}`
)
setTimeout(() => {
setMessage(null)
}, 5000)
setError(true)
setNewName('')
setNewNumber('')
} else {
setMessage(
`Information of ${samePerson.name} has already been removed from server`
)
setTimeout(() => {
setMessage(null)
}, 5000);
setPersons(persons.filter(person => person.name !== newName))
setNewName('')
setNewNumber('')
setError(true)
}**
})

降低冗余后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const samePerson = persons.find(person => person.name === newName)
const changedPerson = { ...samePerson, number: newNumber }

personService
.update(samePerson.id, changedPerson)
.then(returnedPerson => {
console.log(returnedPerson)
setPersons(persons.map(person => person.id === samePerson.id ? returnedPerson : person))
setNewName('')
setNewNumber('')
setError(false)
setMessage(
`Updated ${returnedPerson.name}'s number`
)
setTimeout(() => {
setMessage(null)
}, 5000)
})
.catch(error => {
// console.log(error.response.data.error)
**if (error.response.data.error) {
setMessage(
`${error.response.data.error}`
)
} else {
setMessage(
`Information of ${samePerson.name} has already been removed from server`
)
setPersons(persons.filter(person => person.name !== newName))
}

setTimeout(() => {
setMessage(null)
}, 5000)
setError(true)
setNewName('')
setNewNumber('')**
})

Untitled