CouchApp: List function with include_docs=true - couchdb

I'm writing a simple CMS CouchApp as a pet project. I'm running CouchDB 1.2.0 on OS X 10.7.4 and using the Python CouchApp development tool.
Basically I wanna have pages as documents and a page would refer to another document that contains the shared layout of the site.
Example of a page document:
{
"_id": "index",
"_rev": "3-d5451ea54212ae6ec0d8d2d95c5f225d",
"content": "<img src=\"/images/img.jpg\"/> <p>Lorem ipsum dolor sit amet.</p>",
"layout": "layouts/default"
}
Example of a layout document:
{
"_id": "layouts/default",
"_rev": "1-d2fa96e15ab8768828b262d81265f3d2",
"content": "<!DOCTYPE html> <html><head> <title>Foo</title> </head><body><div>{{content}}</div></body> </html>"
}
So basically to render a page I will need to fetch two documents. I would then render the page into the layout using Mustache.
I fiddled around with show functions for a while but couldn't figure out a way to get the layout document in the function. I then stumbled upon include_docs and I'm now trying to get this to work using a list function and a view. My map function is as follows:
function(doc) {
if (doc.layout) {
emit([doc._id, 1], doc);
emit([doc._id, 0], {_id: doc.layout });
}
};
When I navigate to the view itself in a browser giving the parameters include_docs=true&startkey=["index",0]&endkey=["index",1], it works just fine and is loading the layout documents.
However, the layout document doesn't get passed to list functions. Running this list function on the aforementioned view
function(head, req) {
var doc = null;
var row = getRow();
do
{
if (!row.value.layout){
doc = row.value;
}
} while (row = getRow())
for (i in doc) {
send(i);
}
}
... with the same parameters renders:
_id
I did a bit of googling and noticed that there was a bug in CouchDB where linked documents didn't get passed onto the list function. As far as I can tell, this should be fixed already. Is this a case of regression or me being retarded?

The value of the row is stored in row.value, but the doc for the row is in row.doc. :)

Related

what is the return value of ArangoJS collection.save()?

The documentation is located here:
Document Manipulation ยท ArangoDB v3.4.1 Drivers Documentation
I see the documentation for collection.replace() and collection.update(), but nothing for collection.save(). I know the save function exits because I'm using it. But it doesn't return the expected value and I'd like to reference the documentation.
My specific problem is that I want to save a document to the ArangoDB database and get back the saved document in full. Here's what I have so far:
async createDocument(collectionName, data) {
try {
const collection = this.db.collection(collectionName);
return collection.save(data); //I want to return the saved document
} catch(err) {
console.log(err.message, "saving failed")
}
}
The documentation of the save method is found under DocumentCollection:
https://docs.arangodb.com/3.4/Drivers/JS/Reference/Collection/DocumentCollection.html#documentcollectionsave
The answer you look for:
returns an object containing the document's metadata
I admit this isn't very detailed. What it returns are the system attributes _id, _key and _rev. This also applies if you save an edge with a _from and a _to attribute, they are not returned as meta data, nor any user attributes even if their names start with an underscore.
If you want it to return the full document, then set the option returnNew:
collection.save(data, { returnNew: true} );
If set to true, return additionally the complete new documents under the attribute new in the result.
The result looks like this:
{
"_id": "coll/123",
"_key": "123",
"_rev": "_YDWaEaa--B",
"new": {
"_id": "coll/123",
"_key": "123",
"_rev": "_YDWaEaa--B",
"foo": "bar"
}
}

Trying to update a mongodb document field via pug/node.js button click

This is a document from my todo mongodb:
"_id" : ObjectId("5a1e96f856f24c43b886eb54"),
"title" : "Drink coffee",
"note" : "Tomorrow morning",
"open" : true,
"duedate" : "2017-12-03"
This is the pug code that populates a todo list page
each todo in todolist
if todo.open
div
a(href="#") #{todo.title}
div
p(style='white-space:pre;')
| #{todo.note}
| Due: #{todo.duedate}
button(id="doneButton") Done
The above works fine. The todo page displays all todo items as expected.
What I want to do:
When user clicks on 'Done' button, I want to update that particular document to "open": false.
(I want to do this onclick, and not by loading the item on a new edit page with an edit button. When todo list page reloads, that todo item is removed from the list. I do not want to delete the document as I need to archive it later on.)
So my questions are:
How do I code the 'Done' button on the pug page so that it gets
associated with the particular document that needs to be updated?
How do I structure the POST code on my index.js so that it listens for
the button click and performs the relevant document update?
UPDATE
OK, so I don't know enough to understand kentor's reply (but thank you anyway!). I did a bit of research though and some copying and pasting, and I have moved the problem a couple of steps forward - I hope.
New pug code:
each todo in todolist
if todo.open
a(href="#") #{todo.title}
p #{todo.note}
p Due: #{todo.duedate}
form#form_update_item(name="updateitem", method='post', action='/updateitem')
input#input_name(type="hidden", placeholder="", name="_id", value="#{todo._id}")
button(type="submit") Done
index.js code
router.post('/updateitem', function(req, res) {
var MongoClient = mongodb.MongoClient;
var ObjectId = require('mongodb').ObjectId;
var url = 'mongodb://localhost:27017/tododb';
MongoClient.connect(url, function(err, db) {
if (err) {
console.log("can't connect", err);
}
else {
console.log('Connected to server. Woohoo!');
var collection = db.collection('todolist');
collection.findOneAndUpdate(
{"title": "Make smoothie"},
{
$set: {
"open": false
}
},
function(err, result) {
if (err) {
console.log(err);
} else {
res.redirect("todolist");
}
db.close();
});
}
});
});
What happens now:
On button click, doc with title "Make Smoothie" changes to "open": false. So button triggers the change I want. Bang! But this is only a partial solution to help me isolate the problem.
What I still need:
On button click, I want the doc's ID whose button was clicked to replace {"title": "Make smoothie"} so that the "open": false change can be made. Something like {_id: "doc ID coming from button blah blah"}.
UPDATE 2
Discovered that I was using Jade syntax not Pug, so instead of this
value="#{todo._id}
I should be using
value=todo._id
So now index.js can console.log the ID the Pug form is passing. Last challenge is to use that ID to change the corresponding mongodb document as described above.
I finally solved it
Last step, I replaced
{"title": "Make smoothie"}
with
{"_id": ObjectId(req.body._id)}
and sure enough, every time I click on a button, the server updates the corresponding document's "open": false
You could just attach a data attribute to the HTML button like this:
button(id="doneButton", data-id=todo._id) Done
Then you would just attach an event listener to this button and send a POST request containing this data-id. Using Jquery it could look like this:
.script
$('#doneButton').on("click", function (element) {
var data = $(this).data("id");
$.ajax({
type: "POST",
url: "your-url",
data: { todoId: data },
success: responseHandler
})
})

IBM Bluemix Discovery - query parameter

I have created a Discovery service on my bluemix account. I want to query my documents from a nodejs application.
I have built a query with some aggregation, tested it using the bluemix online tool and it's working well.
Now when I query the collection from my code, whatever my parameters are, I always receive all of my documents with the enriched text and so on. I think I am missing how to send the query attributes to the service (like filters and aggregations).
Here is my code:
var queryParams = {
query:'CHLOE RICHARDS',
return:'title',
count:1,
aggregations:'nested(enriched_text.entities).filter(enriched_text.entities.type:Person).term(enriched_text.entities.text, count:5)'
};
discovery.query({environment_id:that.environment_id, collection_id:that.collection_id, query_options:queryParams }, function(error, data) {
if(error){
console.error(error);
reject(error);
}
else{
console.log(JSON.stringify(data, null, 2));
resolve(data.matching_results);
}
});
And the result is always:
{
"matching_results": 28,
"results": [
{
"id": "fe5e2a38e6cccfbd97dbdd0c33c9c8fd",
"score": 1,
"extracted_metadata": {
"publicationdate": "2016-01-05",
"sha1": "28434b0a7e2a94dd62cabe9b5a82e98766584dd412",
"author": "Richardson, Heather S",
"filename": "whatever.docx",
"file_type": "word",
"title": "no title"
},
"text": "......
Independantly of the value of the query_optionparameter. Can you help me?
EDIT
Instead of the query_options:queryParams, I have used query:"text:CHLOE RICHARDS" and it's working well. Now my problem still remains to find the right parameter format to add the aggregations I want
EDIT 2
So I have looked at IBM's example on Github more carefully, and the parameters are now formatted like this:
const queryParams = {
count: 5,
return: 'title,enrichedTitle.text',
query: '"CHLOE RICHARDS"',
aggregations: [ 'nested(enriched_text.entities).filter(enriched_text.entities.type:Person).term(enriched_text.entities.text, count:5)' ],
environment_id: '1111111111',
collection_id: '11111111111'
};
It works well if I use only the query attribute. Now if I only use the aggregations one, all the documents are sent back as a result (which is understandable) but I have no aggregation part, so I can not access the list of proper name in my documents.
Your query does not look right. I you are going to use query then you will need to construct a query search like text:"CHLOE RICHARDS"
If you want to perform a natural language query then you should be setting the parameter natural_language_query.

error in filtered function creation on couchdb

I'm trying to follow this tutorial to filter the replication between a pouchdb and a couchdb databases
https://pouchdb.com/2015/04/05/filtered-replication.html
The problem is when I try to create the filtered function in the Fauxton webapp. In my created database, I click Design Document > New Docs and then paste the function:
{
"_id": "_design/app",
"filters": {
"by_agent": function(doc, req) {
return doc.agent === req.query.agent;
}.toString()
}
}
and when I click Create Document button, it crashes. The javascript console says
Uncaught SyntaxError: Unexpected token u in JSON at position 61
at JSON.parse ()
at t.checkDocIsValid (https://127.0.0.1:6984/_utils/dashboard.assets/js/bundle-b8e0ba71119195edb7ec64b98f53d9b9.js:529:19481)
at t.saveDoc (https://127.0.0.1:6984/_utils/dashboard.assets/js/bundle-b8e0ba71119195edb7ec64b98f53d9b9.js:529:19056)
...
how do I create the filtered function in couchDB? Maybe that isn't the procedure or I have to create it on another dababase. Thanks in advance
So what you're trying to do is use JavaScript code to create a view. Therefore, Fauxton takes only JSON as a document.
Here's how you can get the JSON from the JavaScript snippet :
//The snippet you had was a JavaScript object
//Even if it seems like a JSON object, there is a function() declaration followed by a .toString()
//By doing so, it easier to write functions instead of writing them in a raw string.
var javascriptObject = {
"_id": "_design/app",
"filters": {
"by_agent": function(doc, req) {
return doc.agent === req.query.agent;
}.toString()
}
}
console.info("You should use the following string in your Fauxton Editor:");
console.log(JSON.stringify(javascriptObject));
You should use the following string instead of the JavaScript snippet you tried:
{
"_id": "_design/app",
"filters": {
"by_agent": "function (doc, req) {\n return doc.agent === req.query.agent;\n }"
}
}

Combine Mongo Output with Node for API

I''m really new to Node but I currently have a NodeJS / Express open source CMS and would like to output some API data for an app that I am working. Forgive me if I'm not using the correct terminology or whatnot, this is new to me.
What I currently have are two collections, locations and tours. The CMS allows me to create a relationship between the two. This simply stores an array of ObjectID's in the locations record for each associated tour record.
What I want to do is take my API output code (below) and have it output the entire tours array, complete with all the fields (title, description, etc), in with each location record. Currently it only outputs an array of the ID's.
Here is my current code:
var async = require('async'),
landmark = require('keystone');
var Location = keystone.list('Location'),
Tour = keystone.list('Tour');
/**
* List Locations
*/
exports.list = function(req, res) {
Location.model.find(function(err, items) {
if (err) return res.apiError('database error', err);
res.apiResponse({
locations: items
});
});
}
/**
* Get Location by ID
*/
exports.get = function(req, res) {
Location.model.findById(req.params.id).exec(function(err, item) {
if (err) return res.apiError('database error', err);
if (!item) return res.apiError('not found');
res.apiResponse({
location: item
});
});
}
Current API output (truncated):
{
"locations": [
{
"_id": "53a47997ebe91d8a4a26d251",
"slug": "test-location",
"lastModified": "2014-06-20T20:19:14.484Z",
"commonName": "test location",
"__v": 3,
"url": "",
"tours": [
"53a47963ebe91d8a4a26d250"
],
"images": []
}
]
}
What I'm looking for:
{
"locations": [
{
"_id": "53a47997ebe91d8a4a26d251",
"slug": "test-location",
"lastModified": "2014-06-20T20:19:14.484Z",
"commonName": "test location",
"__v": 3,
"url": "",
"tours": [
{
"_id": "53a47963ebe91d8a4a26d250",
"title": "my test tour title",
"url": "url_to_audio_file"
}
],
"images": []
}
]
}
Anyone know if this is possible? Any help would be appreciated! Thanks!
It looks like you have setup your Location model to have a reference to the Tours, defined as an array of Tours. This means that when you store the Tour within your Location, you're not storing the data that represents that Tour, but instead an ID that references the Tour. When you perform the find operation, you're seeing that in the response that you send back to the client.
If this is the case, then you might want to take a look at Mongoose's populate function. This will take those references and populate them fully with the data that they contain.
So for instance, you can change your query to the following:
Location.model.find().populate('tours').exec(function(err, items) {
// items should now contain fully populated tours
}
Let me know if this isn't what you mean and I can try to help further.
The solution provided by #dylants is absolutely correct. However, for it to work you need to have tours declared as a Types.Relationship field in your Location list with the ref option set to Tour.
Check out the Keystone docs on Relationship Fields.
I included the many: true option in my example below, because I assumed this is a one-to-many relationship. If it isn't, you can discard it.
var keystone = require('keystone'),
Location = keystone.list('Location');
Location.add({
...
tours: { type: Types.Relationship, ref: 'Tour', many: true },
...
});
The List.relationship() method you mentioned is meant to be used only if you want a list of related documents to automatically appear in the Keystone Admin UI, and not to establish the actual relationship.
Hope this helps.

Resources