flatiron / resourceful

an isomorphic Resource engine for JavaScript
http://github.com/flatiron/resourceful
Apache License 2.0
354 stars 56 forks source link

Error when calling Couch views #27

Open richmarr opened 12 years ago

richmarr commented 12 years ago

It's likely this is user error, but I haven't figured it out yet. I'm getting this error when trying to access a CouchDB view I created.

TypeError: Object #<Object> has no method 'view'
    at /Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/engines/couchdb/view.js:87:14
    at EventEmitter.<anonymous> (/Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/resource.js:498:17)
    at EventEmitter.g (events.js:143:14)
    at EventEmitter.emit (events.js:64:17)
    at Function.emit (/Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/core.js:159:33)
    at /Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/resource.js:513:10
    at /Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/engines/couchdb/index.js:167:9
    at /Users/richardmarr/Projects/web-app/node_modules/resourceful/node_modules/cradle/lib/cradle.js:415:29
    at IncomingMessage.<anonymous> (/Users/richardmarr/Projects/web-app/node_modules/resourceful/node_modules/cradle/lib/cradle.js:253:72)
    at IncomingMessage.emit (events.js:81:20)

The view creation code looks like this:

User.filter( 'byTwitterId', {map:function(doc){
    if ( doc.resource == "User" && doc.auth && doc.auth.twitter ) emit( doc.auth.twitter.id, doc);
}});

...and the view lookup code looks like this:

User.byTwitterId({key:id},function(){})
richmarr commented 12 years ago

In the scope of this error the that variable (view.js line 87) which has no method 'view' appears to be the global context, i.e. its properties are things like process and setTimeout. Changing that to R so that it references the specific Resource seems to make the error go away.

I'm guessing it was written this way for a reason, so my question is probably what could I have done to knock the that variable out of orbit.

tblobaum commented 12 years ago

Can you post the code which includes that and R?

richmarr commented 12 years ago

This is a commit that makes this problem go away for me: https://github.com/richmarr/resourceful/commit/fde8d6ea794bf12a4b99cd5e3bdcdd1133343494

I won't claim to fully grok the context though so this may well be the wrong change to make.

distracteddev commented 12 years ago

I thought I should clear this issue up as I was confused by a similar problem earlier. These confusions arise because there are many .filter() methods throughout the resourceful code base, and it is unclear how and when each of them are to be used. Furthermore, there are no examples in the docs showing users how to correctly create their own filters via a custom MapReduce function or the simplified/short-cut version where you simply pass in an example property and value to match the documents against (However, there are perfect examples of this within the tests provided).

What should have been done is the filter should have been set when defining the Resourceful model. The example in the tests gives us:

  Article = resourceful.define('Article', function () {
    this.use('couchdb', 'couchdb://127.0.0.1:5984/test');
    this.property('author');
    this.property('title');
    this.property('published', Boolean);

    this.filter('all', {});
    this.filter('published', { published: true });
    this.filter('by', function (author) { return { author: author } });
    // This would be the correct way to implement richmarr's view
    this.filter('byTwitterId', {include_docs: true}, {map: function(doc) {
         if ( doc.resource == "User" && doc.auth && doc.auth.twitter ) emit( doc.auth.twitter.id, doc);
    }});
    // Or more Simply with...
    this.filter('byTwitterIdSimple', {include_docs: true}, function (id) { return {auth.twitter.id: id}});
 });

What this is actually doing is calling the .filter() function found in resourceful/enginges/couchdb/view.js which then creates a named filter. This filter can then be called as the following:

  Article.byTwiiterId({key: id}, callback(err, articles){
       // Do stuff with articles here
  });

  // Or more simply with,
  Article.byTwitterIdSimple(id, callback(err, articles){
       // Do stuff with articles here
  });
richmarr commented 12 years ago

@distracteddev so you're saying that Article.filter() doesn't work, and that filters can only be declared in the define() block when the resource is defined?

distracteddev commented 12 years ago

Correct, you generally don't want to create filter()'s outside of your define block. You'll notice that .filter() is not listed under the API of a Resource constructor or a Resource instance. Instead, you can use find() if you are looking for a particular instance. Also, if you are really doing something that needs full featured dynamic view creation, then you can probably find a a way of "re-opening" a resourceful class instance and adding another filter onto its "definition". Or, more simply, you could run find() and then run your crazy dynamic map/reduce function on the response (Not sure what effect this would have on performance, if any).

Why? Since Resourceful was built with an extremely documented orientated mindset, filters() are tightly correlated with views in couchDB. Thus, creating views outside of your model would be akin to having a temporary view. This is never a good idea and you are warned against doing so by the CouchDB docs.

If you MUST have DB-native temporary views I have definitely seen some code related to creating dynamic temporary views. However, its definitely undocumented and I don't see it being used in the tests, so its unclear how robust using these functions would be. But truly, if you think you need a temporary view, you probably don't.

richmarr commented 12 years ago

@distracteddev hold up. Are you saying that calling this.filter() within the define() block creates a persistent view but Article.filter() after initial definition creates a temporary view? If so that doesn't fit with my experience, I'm declaring filters outside the define() block in my app and they are definitely persisted to my design docs in CouchDB.

distracteddev commented 12 years ago

I'm saying that Article.filter(), although a provided function, is not truly part of the exposed API. It's a function used internally by Resourceful, which is why it exists, but it doesn't seem to be part of the standard library usage. Thus, I cannot comment on what might happen when one does call filter() on a Resource constructor post-initialization, however, what I do know is that it is completely different than the exports.filter() function that is defined in resourceful/couchdb/engines/view.js. The latter is the function that is used when calling this.filter() within a define block, whereas Resource.filter() is called when calling it on an actual defined constructor.

If you want to figure out how something like Article.filter() was intended to be used, just comb the code base for examples and try to mimic their arguments and structure. Then you can use it on your own, but just be warned that it might not have been designed to accept generic use cases and as such might be prone to error when trying to stray from examples provided.

richmarr commented 12 years ago

@distracteddev so you're saying that the error is because I'm using unsupported behaviour? I have to ask, what's your relationship to the project? Are you a committer? A Nodejitsu employee?

distracteddev commented 12 years ago

Yes, correct. Me? just a resourceful user that had this same issue (although I do have a small fix related to aggregate couch views waiting to be merged in). I was trying to get something like Article.filter() to work but it failed on me for a simple map-reduce filter so I knew something was up. I spoke to several nodejitsu employees and they never once mentioned creating a filter via Resource.filter(). Furthermore, this use of Resource.filter() is not included in any of the tests, which further suggests that it wasn't intended to be supported, at least not yet.

After looking into it, it seems that Resource.filter() ends up passing the filter along with your options to the native Cradle connection interface. Furthermore, I know there is a open issue related to exposing this kind of a native Cradle interface where indexzero states that the feature is currently half-baked. Perhaps these two things are related.

Edit: Just looking at Resource.filter(), it doesn't make sense to be used in the way you are trying to as it doesn't ever create a function with the provided name on the resource itself? i.e. After calling resource.filter(name, function), the function resource.name is never created. Is this your experience? or am I missing something?

2nd Edit: Interesting, after looking at your initial stack-trace, it seems as though the named view function was infact created on the resource, however, it was called on an object that does not have a .view() method defined. This kind of internally broken behavior seems to confirm my suspicion that the intended method of creating custom views is through the this.filter() option when defining a resource. It would be nice to get some official clarification on this.