views:

86

answers:

2

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"] }
+2  A: 

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...
  }
]
jhs
This is a cool new feature, but I don't want to return the entire document. I just want a list of favorite items with the title only. I only need the entire document if a user selects to view a document.
Jeremy Raymond
Yes, I understand. Also my solution would force you to upgrade of course. But I am unsure how you can accomplish what you want without a "schema" refactor.
jhs
I'd consider refactoring how the docs are arranged to accommodate this. Any suggestions?
Jeremy Raymond
I'll add another answer then.
jhs
It'd be cool if you could do an include_docs but then specify a sub-set of the fields of the doc you want included in the view.
Jeremy Raymond
A: 

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]);
        }
    }
}
jhs
Mightn't I run into scalability issues here? If a lot of users are trying to add this same document as a favorite I may run into a lot of conflicts.
Jeremy Raymond
Yes, you are right. It is a trade-off which depends on your expected usage. Another idea is to store it the other way: store `["doc_id", "Doc Title"]` tuples in the user object. That would require three round-trips (1: fetch the main doc; 2: fetch the user doc, 3: update the user doc with the new tuple) however you might already have all those documents handy at the necessary time. I would still prefer this compared to multiple lookups at read time via your join-style documents.
jhs
I was hoping for a single read operation on favorite lookups. I was also trying to avoid copying the doc title into other docs because if the title gets updated in the actual doc then the copied title is out of date or I need to find and update all the user docs with this doc as a favorite.
Jeremy Raymond
What I have now is one read to the view to find the favorite doc IDs for a user. Then for each favorite I'm looking up the title with another read.
Jeremy Raymond