1602 / jugglingdb

Multi-database ORM for nodejs: redis, mongodb, mysql, sqlite3, postgresql, arango, in-memory...
http://1602.github.io/jugglingdb/
2.04k stars 241 forks source link

Feature: promises for related records #181

Open dasher opened 11 years ago

dasher commented 11 years ago

Currently, to display parent-child references in views, when using jugglingDB in CompoundJS, we need to do something like: controller:

  User.findOne({userId: id}, function(err, user){
    user.Posts.all(
      function(posts){
        this.posts = posts;
        render({user: user, posts: posts})
      })
  })

allowing in the view:

  .li #{user.name}
    ul.posts
      - each post in posts
        li.post #{post.title}

Which while not awful for a single relationship is grim - controllers have to force load & attach child records in order to access them in views. As you add more complex or nested relationships it becomes difficult to keep clear separation between controllers & stay dry. Compound makes sharing code between controllers really simple - so you can implement patterns to minimise overlap - but it's a bit of a band aid.

A nicer solution would be for jugglingDB to return promises - for relationships - which allow implicit loading of associated child records. With such an approach - it would allow the controller to look more like:

  User.findOne({userId: id}, {withChildren: true}, function(err, user){
    render({user: user})
  })

and the view being:

  .li #{user.name}
    ul.posts
      - each post in user.posts
        li.post #{post.title}

Here I've gone for an explicit approach to requesting child relationships when fetching the user - to balance the fear/concern about memory or runtime performance.

1602 commented 11 years ago

I think it could be done as separate api call, at least on prototyping stage:

Model.loadWithDependencies(recordId, {options}, function (err, model) {
});

This method will generate additional queries to adapter for each dependency. I know, in sql we can load all 'belongsTo' and 'hasOne' records, in redis we can load everything (including collections) in single transaction, i'm not sure about mongodb and others. But it's clear that each adapter should be able to handle it separately. This is why i don't want to touch existing method, at least while we prototyping this feature.

Let me know what do you thing about it.

dasher commented 11 years ago

Sounds good - the other approach I was thinking of was something helper based in compound but I don't think it would work well. A separate method allows for a cleaner implementation.

mongo supports a multi-fetch in a single statement.

What do you think about something promise based than cb based? With the right promise lib - it'll be fulfilled and properties on objects will be fulfilled without any special syntax.

So you'd end up with something like: Model.loadWithDependencies(recordId, {options}) .then(function() { render({}); })

and the child records would be:

Under the covers - a cb approach would stall until the child records have been fulfilled - a promise approach would return a promise - each of the child relationships would be filled in their own time - only stalling when you try to access a property.

Everyauth has a good example of using promises - and there's a lib that I've used with a pretty fluid interface: github.com/kriskowal/q

I tout promises with this - because I have a system with around 200M mongo records :/