Creating a validation with CouchDB (Fauxton) - couchdb

I store products in the following way in a CouchDB database:
{
"_id": "1ce330a867a803fd10082c4513000fe5",
"_rev": "4-a5eae6f790ea8b9cedea53db50a13fef",
"type": "Product",
"name": "Apple",
"price": 1
}
I'd like to create a validation to make sure that every new document with the type Product has the field name. The only documentation I found was http://guide.couchdb.org/draft/validation.html After reading it I wrote/copied this validation:
function(newDoc, oldDoc, userCtx) {
function require(field, message) {
message = message || "Document must have a " + field;
if (!newDoc[field]) throw({forbidden : message});
}
if (newDoc.type == "Product") {
require("name");
}
}
Than I use Fauxton to create it:
But it doesn't work. I can create such a document without a name field. What do I do wrong?

You have to create a new design document. You need to specify the language field which will be JavaScript in your case.
Then, you have to add the validate_doc_update field and fill it with your function.
As a reference, you can look into the _replicator database for the _design/_replicator.
Alternative
On the command line it can be done with (beware of escaping the "):
curl -X PUT http://127.0.0.1:5984/shop/_design/product_validation -d '{"validate_doc_update": "function(newDoc, oldDoc, userCtx) { if (newDoc.type == \"product\") { if (!newDoc[\"name\"]) { throw({forbidden: \"missing name\"}) } } }"}'

Related

CouchDB/PouchDB design documents query with startkey endkey not working

In my database I have (not only) two types of documents in a one-to-many relationship. I tried to manage this like the following example shows:
{
_id : "class:xyz"
type: "class"
... other keys ...
}
{
_id : "class:xyz:pupil:abc"
type: "pupil"
... other keys ...
}
If I query my docs with allDocs() like
http://url_of_my_server:5984/my_database/_all_docs?include_docs=true&?startkey="class:xyz:pupil:"&endkey="class:xyz:pupil:\ufff0"
I get all docs of type pupil related to the mentioned doc of type class like wanted.
For performance I had the idea to introduce a design document to query only the docs of type pupil and not all docs every time:
{
"_id": "_design/types",
"language": "javascript",
"views": {
"classes": {
"map": "function(doc){ if(doc.type == \"class\"){emit(doc.id, doc);} }"
},
"pupils": {
"map": "function(doc){ if(doc.type == \"pupil\"){emit(doc.id, doc);} }"
},
}
}
But if I query with this design document like
http://url_of_my_server:5984/my_database/_design/types/_view/pupils?include_docs=true&?startkey="class:xyz:pupil:"&endkey="class:xyz:pupil:\ufff0"
I get no result (only an empty array/no rows).
Where is my mistake or what is wrong in my conception? I actually have no idea? Thanks in advance for your advice.
First, for a view never emit the document as a value; it is quite redundant since one may use include_docs=true - and worse, it consumes storage unnecessarily.
Assuming two documents
{
_id : "class:xyz:pupil:abc"
type: "pupil"
},
{
_id : "class:xyz:pupil:xyz"
type: "pupil"
}
And the map function for pupils
"pupils": {
"map": "function(doc){ if(doc.type == \"pupil\"){emit(doc.id, doc);} }"
}
The view index for pupils looks like this
id
key
value
class:xyz:pupil:abc
null
{"_id": "class:xyz:pupil:abc", /* etc */ }
class:xyz:pupil:xyz
null
{"_id": "class:xyz:pupil:xyz", /* etc */ }
So the key columns are null because the map function is emitting doc.id as the key.
See it? doc.id is undefined - emit(doc._id) instead.
Fix the design document map functions (including not emitting doc as value)
{
"_id": "_design/types",
"views": {
"classes": {
"map": "function(doc){ if(doc.type == \"class\"){emit(doc._id);} }"
},
"pupils": {
"map": "function(doc){ if(doc.type == \"pupil\"){emit(doc._id);} }"
}
}
}
Given the two hypothetical documents, the pupils index now looks like this
id
key
value
class:xyz:pupil:abc
class:xyz:pupil:abc
null
class:xyz:pupil:xyz
class:xyz:pupil:abc
null
Now that the view index's key has a value the query will operate as intended.
I highly recommend using Fauxton as it provides a quick means to view an index among other things.

Finding Overlap date ranges between startdate and enddate in couch Db or cloudant

Hello I am building a reservation app using database as couchDb. I have several reservation documents and each of them has roomId, start date and end date.
Now when user creates a meeting request with roomId, start date and end date, I need to search for overlaps time ranges between the start time and endtime in the existing reservations and create a reservations only when there is no conflict. Along with this I also need to check for roomid.
The requirement is similar to Determine Whether Two Date Ranges Overlap.
I had created a view on my couch db emitting three keys:
function (doc) {
if (doc.type == "reservation") {
emit([doc.roomid, doc.startTime, doc.endTime], doc);
}
}
I did try creating something like
?startkey=["1970-01-01T00:00:00Z", ""]&endkey=["\ufff0", "1971-01-01T00:00:00Z"]
However I am not really getting how to compound query the view to find range of date along with the roomid.
Any help would be appreciated.
You could use Cloudant Query and specify the (StartA <= EndB) and (EndA >= StartB) search condition that's outlined in the referenced answer.
Create an index
Send a POST request to the _index endpoint, passing the following JSON data structure as payload.
POST https://$USERNAME:$PASSWORD#$HOST/$DATABASE/_index HTTP/1.1
{
"index": {
"fields": [
{ "name":"startTime",
"type":"string"
},
{
"name":"endTime",
"type":"string"
},
{
"name":"roomid",
"type":"string"
}
]
},
"type": "text"
}
Query the index
Send a POST request to the _find endpoint, passing the following JSON data structure as payload.
POST https://$USERNAME:$PASSWORD#$HOST/$DATABASE/_find HTTP/1.1
{
"selector": {
"startTime": {
"$lte": "2017-03-06T15:00:00Z"
},
"endTime": {
"$gte": "2017-03-06T14:00:00Z"
},
"roomid": {
"$eq": "room 123"
}
}
}
Replace the timestamp and room identifier values as needed. If the query returns at least one document you've encountered a booking conflict.

Editing/Updating nested objects in documents CouchDB (node.js)

I'm trying to add (aka. push to existing array) in couchDB document.
Any feedback is greatly appreciated.
I have a document called "survey" inside my database called "database1".
I have "surveys" as a set of arrays which consists of objects that has information on each survey.
My goal is to update my "survey" document. Not replacing my array, but adding a new object to the existing array. I've used "nano-couchdb" and "node-couchdb", but could not find a way around it. I was able to update my "surveys", but it would replace the whole thing, not keeping the existing objects in array.
1) Using Nano-couchdb:
db.insert({ _id, name }, "survey", function (error, resp) {
if(!error) { console.log("it worked")
} else {
console.log("sad panda")}
})
2) Using couchdb-node:
couch.update("database1", {
_id: "survey",
_rev:"2-29b3a6b2c3a032ed7d02261d9913737f",
surveys: { _id: name name: name }
)
These work well with adding new documents to a database, but doesn't work with adding stuff to existing documents.
{
"_id": "survey",
"_rev": "2-29b3a6b2c3a032ed7d02261d9913737f",
"surveys": [
{
"_id": "1",
"name": "Chris"
},
{
"_id": "2",
"name": "Bob"
},
{
"_id": "1",
"name": "Nick"
}
]
}
I want my request to work as it would for
"surveys.push({_id:"4",name:"harris"})
whenever new data comes in to this document.
Your data model should be improved. In CouchDB it doesn't make much sense to create a huge "surveys" document, but instead store each survey as a separate document. If you need all surveys, just create a view for this. If you use CouchDB 2.0, you can also query for survey documents via Mango.
Your documents could look like this:
{
"_id": "survey.1",
"type": "survey",
"name": "Chris"
}
And your map function would look like that:
function (doc) {
if (doc.type === 'survey') emit(doc._id);
}
Assuming you saved this view as 'surveys' in the design doc '_design/documentLists', you can query it via http://localhost:5984/database1/_design/documentLists/_view/surveys.

CouchDB ACL cross referencing documents

Let's say I have two types of docs with one referencing the other, e.g. "orders" and "order_replies" the later one having a field "ref" which contains an ID of an order document.
For my validate_doc_update function I want a user only to be able to change an order_reply document if he is the author of the original order.
How can I get the "author" field from the order with the ID newDoc.ref within the function?
Here's a part of my validation function so far:
if(userCtx.roles.indexOf('employee') >= 0) {
// [...] other doc types
if (newDoc.type == 'order_reply') {
//newDoc.ref.author is not the proper approach
require((userCtx.name == newDoc.ref.author), "this is not your order reply!");
}
}
Put the author's ID in the order's ID. For example:
The order: { "_id": "order/jack/1", ... }
An order's reply: { "ref": "order/jack/1", ... }
So you can check with:
if (userCtx.roles.indexOf('employee') !== -1) {
if (newDoc.type === 'order_reply') {
require(userCtx.name === newDoc.ref.split('/', 3)[1], "this is not your order reply!");
}
}
If you have multiple authors, use "order/author1/author2/.../authorN/ordernum" as _id of the order and check with:
var parts = newDoc.ref.split('/'),
authors = parts.slice(1, -1);
require(authors.indexOf(userCtx.name) !== -1, "this is not your order reply!");
UPDATE: Due to bug COUCHDB-1229, use of "/" in doc._id may cause problems. Depending on your use-case, it may be better to use another separator, e.g. ":".

CouchDB inner join by document field?

I have a question, i have 2 kind of documents, one of them is like this:
{
"type": "PageType",
"filename": "demo"
"content": "zzz"
}
and another one like this:
{
"type": "PageCommentType",
"refFilename": "demo"
"content": "some comment content"
}
i need to emit document that contains .comments field which is array of PageCommentType documents that i link on condition PageType document filename == PageCommentType document refFilename fields.
{
"filename": "demo",
"comments": [{}, {}, {}]
}
Anyone has any suggestions on how to implement it?
Thank you.
You need view collation. Emit both within the same view, using the filename as the key and a type identifier to discriminate between comments and the original content:
function(doc) {
if (doc.type == "PageType") emit([doc.filename,0],doc.content);
if (doc.type == "PageCommentType") emit[doc.refFilename,1],doc.content);
}
When looking for the document demo and its comments, run a query with startkey=["demo",0] and endkey=["demo",1]: you will get the page content followed by all the comments.
Once you have all the data you want, but it's not in the right format, you are almost done. Simply write a _list function to read all the rows and output the final JSON document with the structure/schema that you need.

Resources