Introduction to CouchDB with .NET part 13: validation functions in design documents
June 12, 2017 1 Comment
Introduction
In the previous post we saw some additional examples of MapReduce functions in CouchDB view design documents. First we investigated the built-in _sum reducer which – as its name applies – can be used to sum up numerical values of the keys from the map phase. The reducer can sum up integers, arrays of integers and objects whose properties are integers. Then we looked at how grouping works and then continued with a brief discussion of custom reducers. We can write our own reducers in JavaScript but we need to make sure that we always return a single value. If we need multiple values from a reducer then can wrap them in a JSON object and return that. Also, the usage of the rereduce parameter is confusing at first and we might only need it in large data sets.
In this post we’ll take a look at another type of functions in design documents, namely validation functions.
Validation functions
A validation function is most often used to validate a modification in the data set. It is called automatically when a new document is added to the database or an existing one is updated. The validation function can check if a certain field is present, if its value is reasonable based on our business logic or if the user is authorised to perform the modification.
Validation functions are enclosed within the same type of design documents as views which we’ve looked at before in this series. Views are denoted by the “views” keyword. Similarly, a validation function is declared using the validate_doc_update property in the design document. Note that there can be only one validation function in a design document.
Demo
For the demo create a new database in Fauxton or via the HTTP API as you wish. Call the database “results”. Then select to add a new design document with the following JSON object:
{ "_id": "_design/validation", "validate_doc_update": "function(newDocument, oldDocument, userContext) { if (!newDocument.points || !newDocument.name) throw({forbidden: 'The points and name fields are compulsory'}) }" }
This is a design document, like the ones we’ve seen up to now so we still adhering to the ID naming rule and start the ID name with “_design”. Then we have the validation function which consists of the following elements:
- newDocument: this is the new document about to be inserted or meant as an update of an existing document
- oldDocument: this is the document that’s being updated
- userContext: the user context to check if a user is allowed to execute the modification. This object includes the properties “name” and “roles” where roles is a string array. We can check if the user has the required role to perform the modification. We’ll look into security later on in this series.
- The function body: we check if the new document has the required properties “points” and “name”. If either of them is missing then we throw a forbidden exception with an error message. Exception types can be either “forbidden” or “unauthorized”
Now try to insert the following document:
{ "name": "John" }
It will fail and Fauxton UI will show the following message:
Next add a valid document:
{ "name": "John", "points": 40 }
The update function works with updates as well. Remove the “name” property from John and try to update the document. It will result in the same exception message.
Let’s extend the validation function with some more checks:
"validate_doc_update": "function(newDocument, oldDocument, userContext) { if (!newDocument.points || !newDocument.name) { throw({forbidden: 'The points and name fields are compulsory'});} if (newDocument.points < 1) { throw({forbidden: 'The points value must be positive'}); }if (oldDocument != null && oldDocument.points > newDocument.points) { throw({forbidden: 'Points cannot decrease'}); } }"
That’s not a pretty sight so I’ll break out the function separately for easier viewing:
function(newDocument, oldDocument, userContext) { if (!newDocument.points || !newDocument.name) { throw({forbidden: 'The points and name fields are compulsory'}); } if (newDocument.points < 1) { throw({forbidden: 'The points value must be positive'}); } if (oldDocument != null && oldDocument.points > newDocument.points) { throw({forbidden: 'Points cannot decrease'}); } }
So we have two new validation steps:
- The first one checks if the points entered is at least 1
- The second one is an example of how we can use the existing document for validation. This rule says that the number of points cannot decrease with an update. We compare the points in the old document and the points in the new document and throw an exception if there’s a decrease
Go ahead and test these cases in Fauxton. You should see exception messages similar to the following:
I think this is a really good function similar to triggers in relational databases.
Read the next part here.
You can view all posts related to data storage on this blog here.
Pingback: CouchDB Weekly News, June 15, 2017 – CouchDB Blog