Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Batch load of nested properties on a collection #75

Closed
scriby opened this issue Aug 12, 2014 · 20 comments
Closed

Batch load of nested properties on a collection #75

scriby opened this issue Aug 12, 2014 · 20 comments
Milestone

Comments

@scriby
Copy link

scriby commented Aug 12, 2014

Can you give me some advice on the best way to handle this scenario?

I have an object A that has a hasOne relationship to a model of type B. Given a collection of A objects, is there a way to load in all the B's?

In my case, the API I'm calling supports a batch load, so I would assume I need to do something custom, but I'm not really sure how to accomplish it using restmod.

Thanks,

Chris

@iobaixas
Copy link
Member

When you talk about preloading a hasOne relation are you expecting object A to contain object B id? If that is the case then you should consider a belongsTo relation (inverse and does not imply a url dependency between objects).

I think this should definitely be a core feature, I'm thinking it should work like this:

var col = Model.$search().$preload('relationName', 'otherRelationName');

then a simple version of preload could be:

$restmod.mixin('Preloader', function() {
  '@$findMany': function(_pks) {
     // not sure about how pks should be serialized.
     return this.$collection().$send({ method: 'GET', url: this.url(), data: _pks }, function(_response) {
       this.$unwrap(_response.data);
     });
  }
  '@$preload': function(_relation) {
     return this.$then(function() {   // I think is better to chain this in the collections promise chain.
       if(this.length == 0) return;
       var foreignType = this[0].$type; // this is probably not the cleanest way, Im yet to implement relation metadata...
       var pks = _.map(this, function(o) { return o[_relation].$pk; }); // using lodash just for simplicity.
       var self = this;
       return foreignType.$findMany(pks).$then(function() {
         var recordMap = {};
         _.each(this, function(objectB) { 
           recordMap[objectB.$pk] = objectB; 
         });

         // using $decode - $encode to copy attributes, maybe it would be better to directly feed the
         // request data to the nested objectB instances, that could be done by mixing the findMany 
         // code with this one.
         _.each(self, function(objectA) { 
           objectA[_relation].$decode(recordMap[objectA[_relation].$pk].$encode());
         });
       )).$promise; // return collection promise so requests are chained.
  });
});

I will add this to the 1.1 milestone.

@iobaixas iobaixas added this to the 1.1 milestone Aug 13, 2014
@fsdf32fee
Copy link

a

@scriby
Copy link
Author

scriby commented Aug 13, 2014

Yes, the A object contains a B id in a property, probably named something like bId.

@jimmybaker
Copy link
Contributor

How do I add this to my models? I've included it in a angular config block which is where I assumed it would go. Please help.

@iobaixas
Copy link
Member

The above mixin declaration is wrong, it should be:

Module.factory('MyMixin', function(restmod) {
  return restmod.mixin({
    // mixin custom merhods here
  });
});

Then to include mixin in every model use rebase in config:

Module.config(function(restmodProvider) {
  restmodProvider.rebase('MyMixin');
});

@scriby
Copy link
Author

scriby commented Sep 15, 2014

It looks like the preload code should use the "key" specified in the relationship, no? (Nevermind... looks like the underlying code is working some magic and setting $pk on the object even before it is loaded)

@scriby
Copy link
Author

scriby commented Sep 15, 2014

return this.$collection().$send({ method: 'GET', url: this.url(), data: _pks }, function(_response) {
  this.$unwrap(_response.data);
});

In that code block, this.url() should be this.$url(). But even after changing that, it doesn't seem to work for "hasOne" / "hasMany" relationships where the path is tacked onto the parent object.

@iobaixas
Copy link
Member

Its intended to be used in reference relations (belongsTo or belongsToMany). Currently hasMany and hasOne do not work with inlined resource ids, maybe they should too..

@scriby
Copy link
Author

scriby commented Sep 15, 2014

Ah, I guess I don't need support for hasOne and hasMany right now. There was just a problem with var foreignType = this[0].$type getting the wrong type. I think that's supposed to be var foreignType = this[0][_relationship].$type.

@scriby
Copy link
Author

scriby commented Sep 15, 2014

Should I have expected objects loaded via the findMany to use the serializers defined by $mix? I'm pretty sure I'm observing that the serializers are not kicking in and date strings are not getting converted to Date objects. (Nevermind... I just had a typo in my $mix definition.)

@scriby
Copy link
Author

scriby commented Sep 15, 2014

Would be nice to be able to call .$preload('a.b') as well. Also, being able to preload arrays within objects would be nice too.

@iobaixas
Copy link
Member

Thanks for the fixes! I hope I can get this done soon!

About the serializers, they should work, could you elaborate more on this?

I agree on supporting $preload('a.b').

@scriby
Copy link
Author

scriby commented Sep 16, 2014

The API I am working with will sometimes pre-fill relationships. For instance, the model has a "users" field, and sometimes the users field is pre-populated with users.

I've noticed that if I try to use preload to load in objects, that the "users" nested within those objects are not coming across. If I fetch the objects normally, the nested users do come across, so it seems to be something specific to how preload works.

Do you have any ideas?

@iobaixas
Copy link
Member

This has to do with using $encode/$decode to transfer data fetched using $findAll to the preloaded records, since relations are not serialized on $encode..

I'm thinking on changing the $findMany method by a $polulate method that fetches the raw records, goes through the packer and then calls $decode on each record being populated with the raw data from the server. I'll give it a try later.

@scriby
Copy link
Author

scriby commented Sep 19, 2014

Is there any way I can adjust the preload code you shared to construct the relationship objects correctly? This is proving to be vital for my use case.

@scriby
Copy link
Author

scriby commented Sep 22, 2014

Thanks for working on this! Any ideas on when you might release this?

@iobaixas
Copy link
Member

Probably later today

@scriby
Copy link
Author

scriby commented Sep 23, 2014

I'm wondering if it is possible to pass extra params to the preload call that will get serialized on the query string. The API I'm using supports just loading specific fields, which is sometimes desirable when preloading relationships.

@iobaixas
Copy link
Member

It could be added, im thinking on something like:

record.$preload(
  'user', 
  { path: 'user.pictures', params: { include_wharever: true }, // preload with options
  'user.paymentData' 
 );

A little verbose though, would also need support from the Polulate plugin. If I have some spare time i'll take a loot at it.

@iobaixas
Copy link
Member

Putting this in a separate issue: #152

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants