genkgo / ember-localforage-adapter

Offline usage for Ember Data, based on localstorage adapter, but now uses Mozilla's localforage as data source
Other
133 stars 26 forks source link

Load association with store.query? #52

Closed oleander closed 8 years ago

oleander commented 8 years ago

I'm trying to load all associated records for a model and it feels like I've tried everything.

Here's is my set-up.

// Route
export default Ember.Route.extend({
  model() {
    return this.store.query("user", { param: 1 });
  }
});

// View
{{#each user in |user|}}
  {{user.posts.length}}
{{/each}}

// User model
export default DS.Model.extend({
  posts: DS.hasMany("post"),
});

user.posts.length is always 0.

I've tried passing {async: true} and {async: false} as argument to DS.hasMany. I tried using the embedded adapter.

import LFSerializer from 'ember-localforage-adapter/serializers/localforage';

export default LFSerializer.extend(
  DS.EmbeddedRecordsMixin, {
    attrs: {
      posts: { embedded: "always" }
    }
  }
);

Something that temporarily worked was to load all associations in a afterModel block. But if I async reload all users the relationship goes away. In example below I reload all users after 5 sec, which results in all posts going away.

// Route
export default Ember.Route.extend({
  model() {
    var store = this.store;
    setTimeout(function(){
      // Reload all users after 5 sec
      store.findAll("user");
    }, 5000);
    return store.query("user", { param: 1 });
  },
  afterModel(){
    this.store.findAll("post");
  }
});

// Isn't zero for 5 sec
{{#each user in |user|}}
  {{user.posts.length}}
{{/each}}

What I'm I missing? I'm using ember 2.1, ember data 2.1 and master version of ember-localforage-adapter.

Thanks.

frederikbosch commented 8 years ago

@oleander I will have a look at this. There was no issue with this in the past. When looking at our tests: this case is not tested in our query integration test. My idea is to add it and see if I run into the same problem as you do. Then we can see how to fix it.

oleander commented 8 years ago

@frederikbosch Thanks! I just tried replacing store.query with store.findAll and the results are the same.

sebweaver commented 8 years ago

@frederikbosch In the past, the adapter preloaded recursively all dependent relationships in the store, by force and no matter the async attributes were. That's why it used to work, in all cases.

Now, with the current implementation of the adapter, I think the issue could be related to coalesceFindRequests and findMany(). Besides, as you said, the length of records involved in a async hasMany relationships are not currently tested, whereas it should. I could look into it ASAP, if you prefer.

@oleander It's odd that your case failed with embedded relationships too. Did you think of migrating the data in your localforage in order to reflect the embedded setting (i.e. actually embed associated records)? I think a jsbin / codepen with your complete setup could help. Anyway, I'll try to figure out why it fails with async relationships (which should be the default in your case).

oleander commented 8 years ago

@sebweaver I've created an example application here: https://github.com/oleander/lffail Run npm install, start the server and visit the index url.

Here's the added code: https://github.com/oleander/lffail/commit/9848885693d953ebd0ea9fdeefd16c642490d1b6

oleander commented 8 years ago

I added another branch called async, which waits for the posts to load before returning the model to the view. Same outcome tho.

sebweaver commented 8 years ago

@oleander I tested your code. It appears you missed some points which are not related to this localforage adapter but rather to how ember data works. Let me explain.

In the branch master (with embedded relationship), you've forgot to associate the post with its owner and then save the latter to the database.

Replace:

     return self.store.createRecord("user", {}).save().then(function(user){
       console.info("created user", user.get("id"));
       return user.get("posts").createRecord({});
     }).then(function(post){
       console.info("with post", post.get("id"));
       return self.store.findAll("user");

By:

     return self.store.createRecord("user", {}).save().then(function(user){
       console.info("created user", user.get("id"));
       var post = user.get("posts").createRecord({});
       user.get("posts").pushObject(post);
       return user.save().then(function() {
         return post;
       };
     }).then(function(post){
       console.info("with post", post.get("id"));
       return self.store.findAll("user");

And your code works as intended.

If you don't want to persist data, you should use peekAll rather than findAll. The difference is the first API only check the content of the store, whereas the second one fetch and refresh data from the source.

In the branch async (with async relationship), you should begin with removing the EmbeddedRecordsMixin which is not relevant/compatible with async relationship.

Replace:

export default LFSerializer.extend(
  DS.EmbeddedRecordsMixin, {
    attrs: {
      posts: { embedded: "always" }
    }
  }
)

By:

export default LFSerializer.extend()

Then, the rest of you issue is pretty the same as above. You should persist the post on its own (since its not embedded), add it to its owner (id will be serialized) and save the latter too.

Replace:

     return self.store.createRecord("user", {}).save().then(function(user){
       console.info("created user", user.get("id"));
       return user.get("posts").createRecord({});
     }).then(function(post){
       console.info("with post", post.get("id"));
       return self.store.findAll("user");

By:

     return self.store.createRecord("user", {}).save().then(function(user){
       console.info("created user", user.get("id"));
       return user.get("posts").createRecord({}).save().then(function(post) {
         user.get("posts").pushObject(post);
         return user.save().then(function() {
           return post;
         });
       });
     }).then(function(post){
       console.info("with post", post.get("id"));
       return self.store.findAll("user");

Voilà!

Don't forget to clear your storage (depending of your browser) between these two tests! Stored data can't be shared between embedded and non embedded models.

Don't hesitate to check your storage too, it could highlight how ember data handles embedded and non embedded relationships.

frederikbosch commented 8 years ago

And if you have chrome, you can inspect the contents of your database by using the Resource tab. There you will see the data popping up in WebSQL or IndexedDB! Good luck!