How can I get a view of favorite user documents by user in Couchdb map/reduce? - couchdb

My Couchdb database as a main document type that looks something like:
{
"_id" : "doc1",
"type" : "main_doc",
"title" : "the first doc"
...
}
There is another type of document that stores user information. I want users to be able to tag documents as favorites. Different users can save the same or different documents as favorites. My idea was to introduce a favorite document to track this something like:
{
"_id" : "fav1",
"type" : "favorite",
"user_id" : "user1",
"doc_id" : "doc1"
}
It's easy enough to create a view with user_id as the key to get a list of their favorite doc IDs. E.g:
function(doc) {
if (doc.type == "favorite") {
emit(doc.user_id, doc.doc_id);
}
}
However I want to list of favorites to display the user_id, doc_id and title from the document. So output something like:
{ "key" : "user1", "value" : ["doc1", "the first doc"] }

In CouchDB 0.11 (just recently released), the include_docs=true feature allows you to look up any document in your view row. For example:
function(doc) {
if(doc.type == "favorite") {
emit(doc.user_id, {_id: doc.doc_id});
}
}
When you query your view with include_docs=true, you should see JSON like this:
// ... normal stuff
rows: [
{
"key":"user1",
"value":{"_id":"doc1"},
"doc": {
"_id" : "doc1",
"type" : "main_doc",
"title" : "the first doc"
// ...
}
},
{
// another doc, etc...
}
]

If you can't use the include_docs=true feature with v0.11, then you must have all information on-hand when you emit data for your view/map.
Instead of a traditional "join" style, consider storing a list of "favoriting" users in the main_doc documents.
{
"_id" : "doc1",
"type" : "main_doc",
"title" : "the first doc",
"favorited_by": ["user1", "user2"]
// ...
}
That way when your view runs, you can emit everything based on the information in that one document.
function(doc) {
if(doc.type == "main_doc") {
for (var a in doc.favorited_by) {
emit(doc.favorited_by[a], [doc._id, doc.title]);
}
}
}

Related

Bulk Save/Update lists of data in MongoDB (Nodejs)

I have lists of data which I want to save and if already exist update.
I can do that using loop. But Is there any other way like insertMany which only supports insert but I want to insert and update too in bulk.
You can use the bulk update feature that Mongo driver provides. Instead of invoking the transactions in a loop, you may add them to a bulk transaction and execute as a batch.
First you need to initialize the bulk operation, ordered / unordered:
var bulk = db.collection.initializeUnorderedBulkOp();
or
var bulk = db.collection.initializeOrderedBulkOp();
Then you can go on adding transactions to the bulk object.
bulk.insert( {
// attributes
} ); // insert operation
or
bulk.find( {
// query attributes
} ).update( {
$set: {
// set attributes
} } ); // update operation
In the end you need to call
bulk.execute();
I don't know if this would serve your purpose.
Please refer this link:
https://docs.mongodb.com/manual/reference/method/Bulk/
Use updateMany with option { upsert: true } which update the document if it is already exists otherwise insert the new document.
Find below example with restaurant collection
{ "_id" : 1, "name" : "Central Perk Cafe", "violations" : 3 }
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "violations" : 2 }
{ "_id" : 3, "name" : "Empire State Sub", "violations" : 5 }
{ "_id" : 4, "name" : "Pizza Rat's Pizzaria", "violations" : 8 }
The query below update the documents with violations equal to 4 and if the document not exists insert new document.
db.restaurant.updateMany(
{ violations: 4 },
{ $set: { "name" : "Eat and Treat" } },
{ upsert: true }
);
Find more details here:
https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/

Mongoose count by subobjects

I am trying to count the number of models in a collection based on a property:
I have an upvote model, that has: post (objectId) and a few other properties.
First, is this good design? Posts could get many upvotes, so I didn’t want to store them in the Post model.
Regardless, I want to count the number of upvotes on posts with a specific property with the following and it’s not working. Any suggestions?
upvote.count({‘post.specialProperty’: mongoose.Types.ObjectId(“id”), function (err, count) {
console.log(count);
});
Post Schema Design
In regards to design. I would design the posts collection for documents to be structured as such:
{
"_id" : ObjectId(),
"proprerty1" : "some value",
"property2" : "some value",
"voteCount" : 1,
"votes": [
{
"voter": ObjectId()// voter Id,
other properties...
}
]
}
You will have an array that will hold objects that can contain info such as voter id and other properties.
Updating
When a posts is updated you could simply increment or decrement the voteCountaccordingly. You can increment by 1 like this:
db.posts.update(
{"_id" : postId},
{
$inc: { voteCount: 1},
$push : {
"votes" : {"voter":ObjectId, "otherproperty": "some value"}
}
}
)
The $inc modifier can be used to change the value for an existing key or to create a new key if it does not already exist. Its very useful for updating votes.
Totaling votes of particular Post Criteria
If you want to total the amount for posts fitting a certain criteria, you must use the Aggregation Framework.
You can get the total like this:
db.posts.aggregate(
[
{
$match : {property1: "some value"}
},
{
$group : {
_id : null,
totalNumberOfVotes : {$sum : "$voteCount" }
}
}
]
)

Mongoose: find mixed schema type documents with multiple entries

My data model looks roughly like this:
data = {
...
parameters: [{type:Schema.Types.mixed}],
...
}
If i now insert a document into the database,
doc = {
...
parameters:[{"foo":"bar"}],
...
}
i can query it via the "parameters" key:
db.dataset.find({"parameters":[{"foo":"bar"}]},function(doc){
...
})
and get back the expected document. However if "parameters" contains more than one key, for example
doc = {
...
parameters:[{"foo":"bar","ding":"dong"}]
...
}
i cant find it anymore. Why?
It is because the query cannot match any documents where the array field parameters has the exact array object as its value [{"foo": "bar", "ding": "dong"}]. To demonstrate this, let's insert a couple of sample documents in a collection:
/* 0 */
{
"_id" : ObjectId("551d777fcfd33f4e2a61e48f"),
"parameters" : [
{
"foo" : "bar"
}
]
}
/* 1 */
{
"_id" : ObjectId("551d777fcfd33f4e2a61e490"),
"parameters" : [
{
"foo" : "bar",
"ding" : "dong"
}
]
}
Querying this collection for parameters array with this object array [{"foo":"bar"}] will bring the document with "_id" : ObjectId("551d777fcfd33f4e2a61e48f"). However, if you change your query object to use $elemMatch then it will bring both documents:
db.collection.find({"parameters": { "$elemMatch": { "foo": "bar" } }});

Node.js - Nested array in Jade view

Using Mongoose, I have a model Page with an embedded model of Feeds. When i go to /pages, the page.title shows up for each page, but feeds data does not. how should i modify this code to properly display the data from the feeds array? thanks a million
db.pages exmaple:
{ "title" : "testing feeds", "_id" : ObjectId("123456"), "feeds" : [
{ "0" : { "name" : "twitter", "key" : "1234" },
"1" : { "name" : "flickr", "key" : "5678" },
}] }
web.js
app.get('/pages.:format?', function(req, res) {
Page.find({}, function(err, pages) {
switch (req.params.format) {
case 'json':
res.send(pages.map(function(d) {
return d.toObject();
}));
break;
default:
res.render('pages/index.jade', {
locals: {
title: 'ClrTouch | Pages',
pages: pages,
feeds: pages.feed,
}
});
}
});
});
view
- each page in pages
div.page
div.pagetitle= page.title
ul
- each feed in page.feeds
li.pagefeedname= feed.name
li.pagefeedkey= feed.key
with what i have, a list is generated in the view but the list items are empty. Thanks.
Is the model that you have here what you are also testing with? If so, then the reason your list is generated but nothing is displayed is because you do not currently have values in either.
li.pagefeedname=feed.name = "0" : { "name" : "", "key" : "" }
Try giving your feed name/keys some values and see how that plays out.
Otherwise what you have should work
tuddy,
I suggest you try like this:
Page.find().lean().exec(function(err,pages){
// TODO
});

Best way to do one-to-many "JOIN" in CouchDB

I am looking for a CouchDB equivalent to "SQL joins".
In my example there are CouchDB documents that are list elements:
{ "type" : "el", "id" : "1", "content" : "first" }
{ "type" : "el", "id" : "2", "content" : "second" }
{ "type" : "el", "id" : "3", "content" : "third" }
There is one document that defines the list:
{ "type" : "list", "elements" : ["2","1"] , "id" : "abc123" }
As you can see the third element was deleted, it is no longer part of the list. So it must not be part of the result. Now I want a view that returns the content elements including the right order.
The result could be:
{ "content" : ["second", "first"] }
In this case the order of the elements is already as it should be. Another possible result:
{ "content" : [{"content" : "first", "order" : 2},{"content" : "second", "order" : 1}] }
I started writing the map function:
map = function (doc) {
if (doc.type === 'el') {
emit(doc.id, {"content" : doc.content}); //emit the id and the content
exit;
}
if (doc.type === 'list') {
for ( var i=0, l=doc.elements.length; i<l; ++i ){
emit(doc.elements[i], { "order" : i }); //emit the id and the order
}
}
}
This is as far as I can get. Can you correct my mistakes and write a reduce function? Remember that the third document must not be part of the result.
Of course you can write a different map function also. But the structure of the documents (one definig element document and an entry document for each entry) cannot be changed.
EDIT: Do not miss JasonSmith's comment to his answer, where he describes how to do this shorter.
Thank you! This is a great example to show off CouchDB 0.11's new
features!
You must use the fetch-related-data feature to reference documents
in the view. Optionally, for more convenient JSON, use a _list function to
clean up the results. See Couchio's writeup on "JOIN"s for details.
Here is the plan:
Firstly, you have a uniqueness contstraint on your el documents. If two of
them have id=2, that's a problem. It is necessary to use
the _id field instead if id. CouchDB will guarantee uniqueness, but also,
the rest of this plan requires _id in order to fetch documents by ID.
{ "type" : "el", "_id" : "1", "content" : "first" }
{ "type" : "el", "_id" : "2", "content" : "second" }
{ "type" : "el", "_id" : "3", "content" : "third" }
If changing the documents to use _id is absolutely impossible, you can
create a simple view to emit(doc.id, doc) and then re-insert that into a
temporary database. This converts id to _id but adds some complexity.
The view emits {"_id": content_id} data keyed on
[list_id, sort_number], to "clump" the lists with their content.
function(doc) {
if(doc.type == 'list') {
for (var i in doc.elements) {
// Link to the el document's id.
var id = doc.elements[i];
emit([doc.id, i], {'_id': id});
}
}
}
Now there is a simple list of el documents, in the correct order. You can
use startkey and endkey if you want to see only a particular list.
curl localhost:5984/x/_design/myapp/_view/els
{"total_rows":2,"offset":0,"rows":[
{"id":"036f3614aeee05344cdfb66fa1002db6","key":["abc123","0"],"value":{"_id":"2"}},
{"id":"036f3614aeee05344cdfb66fa1002db6","key":["abc123","1"],"value":{"_id":"1"}}
]}
To get the el content, query with include_docs=true. Through the magic of
_id, the el documents will load.
curl localhost:5984/x/_design/myapp/_view/els?include_docs=true
{"total_rows":2,"offset":0,"rows":[
{"id":"036f3614aeee05344cdfb66fa1002db6","key":["abc123","0"],"value":{"_id":"2"},"doc":{"_id":"2","_rev":"1-4530dc6946d78f1e97f56568de5a85d9","type":"el","content":"second"}},
{"id":"036f3614aeee05344cdfb66fa1002db6","key":["abc123","1"],"value":{"_id":"1"},"doc":{"_id":"1","_rev":"1-852badd683f22ad4705ed9fcdea5b814","type":"el","content":"first"}}
]}
Notice, this is already all the information you need. If your client is
flexible, you can parse the information out of this JSON. The next optional
step simply reformats it to match what you need.
Use a _list function, which simply reformats the view output. People use them to output XML or HTML however we will make
the JSON more convenient.
function(head, req) {
var headers = {'Content-Type': 'application/json'};
var result;
if(req.query.include_docs != 'true') {
start({'code': 400, headers: headers});
result = {'error': 'I require include_docs=true'};
} else {
start({'headers': headers});
result = {'content': []};
while(row = getRow()) {
result.content.push(row.doc.content);
}
}
send(JSON.stringify(result));
}
The results match. Of course in production you will need startkey and endkey to specify the list you want.
curl -g 'localhost:5984/x/_design/myapp/_list/pretty/els?include_docs=true&startkey=["abc123",""]&endkey=["abc123",{}]'
{"content":["second","first"]}

Resources