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

how debug? #27

Closed masciugo closed 9 years ago

masciugo commented 9 years ago

Hi, I am trying to learn ember while building a spa with ember-localforage-adapter. I have problems like this but I am not able to understand if its a problem with the library or ember-data or my understanding of both.

Working with a remote api as data source would let me see when the requests to the server are fired by ember-data and better understand/debug. Is there any way to log the adapter activity?

thanks

frederikbosch commented 9 years ago

@masciugo In order assess whether it is a problem with the library or your own code, you might just post detailed information on your problem. If it is not a library problem, I will close the ticket and redirect you to some page in the Ember docs.

masciugo commented 9 years ago

No problem, just need help. thank you

frederikbosch commented 9 years ago

@masciugo Why are you closing the ticket? Please, just post your actual question. I am happy to help.

frederikbosch commented 9 years ago

@masciugo I will have a look at your post in the stackoverflow issue, and try to replicate it. If it is the library, there will be a fix.

frederikbosch commented 9 years ago

@masciugo Just to be sure: you are on the latest version 0.7.3 of this adapter?

masciugo commented 9 years ago

Sorry Frederik, fact is that I am at the beginning and I don't feel so confident about the way ember-data works. Have a look to the stackoverflow issue if you can and I would much appreciate. In the meantime I will try to better isolate the problem I hope in a js playground so that, if necessary, I can open a more appropriate issue here. thanks

masciugo commented 9 years ago

ya 0.7.3

frederikbosch commented 9 years ago

@masciugo Actually, I just included a test with exactly your problem posted at stackoverflow, see most recent commit c5ad39f60606710cdc50c567a92e237472705f7a. The tests were passing without any problem. So there must a problem on your side.

masciugo commented 9 years ago

thank you Frederik, I couldn't ask more.

frederikbosch commented 9 years ago

@masciugo No problem, hope you succeed in setting up your app with the localforage adapter!

masciugo commented 9 years ago

Frederik sorry if I bother you. In order to provide detailed information on a problem as you stated above, Is there any way to have ember-localforage-adapter in a jsbin template? I am not an expert but I think it would let me test it better or build critical examples to submit if you are interested. thank you

frederikbosch commented 9 years ago

@masciugo You might try Twiddle, otherwise use JSBin, but then you will have to rewrite the code a bit because ES6/EmberCLI code is not supported at this moment.

frederikbosch commented 9 years ago

@masciugo How are things going for you? Any success on the Twiddle/JSBin, or maybe you already found out what your problem was?

masciugo commented 9 years ago

@frederikbosch thanks for your interest. As I told you I am at the beginning with ember and maybe starting with a local storage system instead of the classic backend API + ember SPA was not a great idea in order to better understand, above all, ember-data. Problem is debugging Ember's magic. Coming from RubyOnRails I'm having hard time with debug and often I have things done by chance. I hate this. To answer your question about Twiddle/JSBin I'm afraid I'm not able to do that.

Anyway my small app is giving me some satisfaction and it is completely available here (demo here)

About your library I have some doubts about the way relationships are stored. When I submit a new purchase I need to also update its relationships. I cannot completely do that. If you uncomment this and this to update the player.purchases it does that as you can see

screen shot 2015-07-08 at 12 15 16

but....

this is the best I can do to show you my current issues.. thanks a lot

frederikbosch commented 9 years ago

@masciugo Maybe I will clone your app and have a look. Looking at your models I find it hard to figure out the structure. Why does a ledger has many players?

My first remark is that I tend not to do any writes on my models, like saving or fetches from the store, inside a component. In my apps this responsibility goes to the Route class. That is where all my persistency stuff goes. The component does interacting with the DOM (mostly though jquery, using this.$()), handles DOM events (clicks, mouseover, scroll etc) and sends information (POJO or Ember.Object) through this.sendAction() to the controller which will eventually bubble up to the route.

When you make that distinction, it will be much easier to read how the interaction with your persistency layers goes. This also means that creating records this.store.createRecord is inside the Route. So the route receiving an ember object containing the data to fill the new record. After creating the record, it can be saved.

masciugo commented 9 years ago

Your remark, as precondition, is fair enough. I'll work on that and let you know. About your first question, every ledger has its own basket of players that can act as payer and/or participants to any purchase. Ledger are independent from each other, that's why, after deleting, I also delete their purchases and players in the didDelete event. I bring this pattern from Rails where an heavy use of model lifecycle callbacks is done to keep consistency among related models.

frederikbosch commented 9 years ago

@masciugo Okay, so ledger is the single aggregate root in your app, right? I do not know exactly how Ember Data handles deletes to be honest. I do not have that many deleteRecord calls in my apps, and when I have them, I remove single entries not object graphs.

You might have a look at embedded records. This might actually be really useful in your case. I believe this also makes sure your relationship calls will not be promises but returns the relation directly. Honestly, I just merged that PR. It looked good and had tests. I have no experience with embedded records in my own apps.

frederikbosch commented 9 years ago

@masciugo Please let me know when you did some changes to the location of your save calls. Then I will clone your lib and see what I can do.

masciugo commented 9 years ago

@frederikbosch embedded records could be a final solution for me and I'll look into that for sure. But now I am in the middle of the Ember steep learning curve and I really need to clear the mysteries I'm stuck into.

So, I followed your suggestions and move persistence directives from component to controller. Thank for that, I learnt a new thing, but the problems described above persit. The two problematic lines are now here and here. Of course any other help or suggestion is for me like a gold mine but don't feel obligated, I'll get out of this sooner or later! thank you

frederikbosch commented 9 years ago

@masciugo So the problem with those two lines is that they have to be commented? Why do you not want them to be commented?

masciugo commented 9 years ago

when I create a new purchase I need to update associated models: ledger and players. Because they both has many purchases. At line 19 the current purchase is added to the ledger's ones and that's ok. Then I want to do the same with any players involved in this purchase: every player should store in its purchases computed property the list of the purchases' ids to whom they participate, so I need to add the current. This process seems so mechanical to me that at the beginning I though that the library should do that for me but I feel I'm missing something important.

masciugo commented 9 years ago

If I uncomment them it stores what I expect as I showed in the picture (even more than I expect actually) but then a fresh reload freezes the app.

masciugo commented 9 years ago

@frederikbosch I made a big discovery which invalidates what I stated in the previous two comments. According to my models relationships setup, when I create a new purchase assigning it ledger, payer, and players I DON'T need leter to update their reverse relationships. My submit action:

submit: function (purchase) {
  var ledger = this.get('ledger');

  Ember.RSVP.all([
    purchase.get('players'),
    ledger.get("purchases")
  ]).then( result => {

    var purchase_players = result[0];
    var ledger_purchases = result[1];

    ledger_purchases.pushObject(purchase);
    purchase_players.getEach("purchases").invoke('pushObject',purchase);

    Ember.RSVP.all([
      purchase.save(),
      ledger.save(),
      // purchase_players.invoke('save')          
    ]).then(() => {

      this.transitionToRoute('ledger.purchases');

    });
  });

}

becomes simply:

submit: function (purchase) {
  var ledger = this.get('ledger');
  purchase.get('players').then( purchase_players => {

    Ember.RSVP.all([
      purchase.save(),
      ledger.save(),
      // purchase_players.invoke('save')          
    ]).then(() => {

      this.transitionToRoute('ledger.purchases');

    });
  });

}

So, for example, I don't need ledger_purchases.pushObject(purchase); and then save the ledger. Only saving it lead me to my desired result (red arrow):

screen shot 2015-07-09 at 14 08 40

Realoding work as expected. Uncommenting purchase_players.invoke('save') and adding a new purchase produce analogue result for the player's relationships purchases and purchasesAsPayer:

screen shot 2015-07-09 at 14 20 05

but now a reload take the whole app to a memory leak. I need to manually clear indexdb to take the control again.

So in the end, as long as the above observed behaviour is correct, the persistence is ok. Problem is realoading which with those relationships populated lead to kind of infinite loop.

frederikbosch commented 9 years ago

@masciugo What do you mean with a reload? Refresh of the page?

masciugo commented 9 years ago

yes, any routes. Chrome task manager shows memory increment till 1.5 GB for the app tab.

frederikbosch commented 9 years ago

@masciugo Maybe an inverse relationship is missing?

masciugo commented 9 years ago

I don't think so.. From the last pictures I pasted, as a consequence of the following declarations, relationships seem correctly stored to me

/app/models/ledger.js

....
purchases: DS.hasMany('purchase', {async: true}),   // A  
players: DS.hasMany('player', {async: true}),    // B
....

/app/models/player.js

ledger: DS.belongsTo('ledger',{ async: true }),    // B 
purchasesAsPayer: DS.hasMany('purchase', {  // C
  async: true,
  inverse: 'payer'
}),
purchases: DS.hasMany('purchase', { async: true }),  // D

/app/models/purchase.js

ledger: DS.belongsTo('ledger', {async: true}),   // A
payer: DS.belongsTo('player', {   // C
  async: true,
  inverse: 'purchasesAsPayer'
}),
players: DS.hasMany('player', {async: true}),  // D
frederikbosch commented 9 years ago

but why not an inverse for the hasMany relations?

masciugo commented 9 years ago

You need to explicitly specify inverse if it cannot be inferred from the name doc. (However, I also tried to explicit inverse with no success). I edited my previous comment with letters which couple relationships.

frederikbosch commented 9 years ago

how about this, works on my side. reloading no problem.

// app/controllers/new.js
import Ember from 'ember';

export default Ember.Controller.extend({
  needs: ['ledger'],
  ledger: Ember.computed.alias("controllers.ledger.model"),

  actions: {
    submit: function (purchase) {
      var ledger = this.get('ledger');

      Ember.RSVP.all([
        purchase.save(),
        ledger.save()
      ]).then(() => {
        this.transitionToRoute('ledger.purchases');
      });

    },
    cancel: function (purchase) {
      purchase.deleteRecord();
      this.transitionToRoute('ledger.purchases');
    }
  }
});
frederikbosch commented 9 years ago

@masciugo You should not save a relationship. Ember data does that for you. The patterns are just a little different compared to Active Record in Ruby. I guess there you have to be explicit about this, because it will fire another request to your SQL database. That is not the case with Ember Data since a collection is serialized into an array. It therefore has more resemblance with a document store than an sql database. However, the api of Ember Data with .save() maybe more looks like Active Record. That is causing the confusion I guess.

So pushing object A to the collection of object B (hasMany) and then save B should be enough.

frederikbosch commented 9 years ago

@masciugo I also advice you to look into your terminal on a regular basis. The jshint advices should be taken into account.

masciugo commented 9 years ago

Yes, in this case I dind't even needed to push the brand new purchase to the the ledger's purchases: it was enough to assign the ledger to the new purchase (belongsTo) and then save purchase and ledger as we did in the code.

Your snippet do exactly the same. I also retrieved purchase_players because I hoped I could safely save player's relationships purchases and purchasesAsPayer so that I can do things like:

// app/controllers/ledger/players/index.js
...
remove: function(player) {
  player.get('purchasesAsPayer').then(  // <=======
    purchases => {
      if (purchases.get('length') != 0) {
        this.notify.alert('Cannot delete player because they paid for ' + purchases.mapBy('name').join(', '));
      } else{
        ....deleting stuff ...
        }); 
      };
    }
  );

}  

But if I don't save players involved in the purchase they remain empty:

screen shot 2015-07-09 at 18 36 26

of course I can replace player.get('purchasesAsPayer') with this.store.find('purchase', {payer: player.id}) but my code may get less readble and, above all, I thought it was a bug. There are for sure other way to get the same result or thing done but I always want to try the more elegant way even when I am no experienced enough.

Anyway, thanks for give my code a try and for your advice.

frederikbosch commented 9 years ago

@masciugo I am lost now. What issue are you still having?

masciugo commented 9 years ago

@frederikbosch I am sorry that I make you lost, really. What I simply would like to do is keeping track of all players' relationships, in particular purchasesAsPayer and purchases and to do that I need to save all players involved in any new purchase, and that's why I need this line of code when create a new purchase. Uncommenting it produce the expected behaviour (red arrow). Problem is refreshing the page which end up in an memory leak I am not able to debug. In few words it seems that those two relationships generate problems when populated. The following is a gif that shows my experience: during the first purchase that line is commented out while during the second one is enabled:

quenta-bug

Consider two things:

frederikbosch commented 9 years ago

@masciugo If there is an issue with the library, I like it to be fixed. There are production apps relying on it. No problem that I am lost. Your movie helps :) Thanks.