Backbone.dualStorage icon indicating copy to clipboard operation
Backbone.dualStorage copied to clipboard

Dualstorage Model without Collections

Open damanic opened this issue 11 years ago • 17 comments

I'm fresh baby bum new to backbone.js so go easy.

I have a customer profile model that I want to use dualstorage on, there is no need for a collection. It appears to me that dual storage does not work unless called through a collection. Is this correct?

As set up below, when loading a view model.fetch() does not call an ajax request with dualstorage enabled. With dual storage removed the data is called in fine.

return Backbone.View.extend({

    initialize: function () {
        this.model.fetch();
        this.model.bind('change', this.render, this);
    },

    render: function () {
        this.$el.html(template(this.model.toJSON()));
        return this;
    }
});

Do I have to set up a collection or?

damanic avatar Mar 20 '14 22:03 damanic

That's strange; it should work on a model that's not in a collection. I assume you made no change to your model for dualstorage? Does your model have a url or urlRoot property?

nilbus avatar Mar 21 '14 00:03 nilbus

The model I am experimenting with:

define(function (require) {

    "use strict";

    var $                   = require('jquery'),
        Backbone            = require('backbone'),
        dualStorage         = require('dualStorage'),

        Customer = Backbone.Model.extend({

            defaults:{
                id: '',
                first_name: '',
                last_name: '',
                email: '',
                company: '',
                phone: ''
            },
            urlRoot: "http://mysite/api_v1/session/customer/",


            initialize: function (attributes,options) {

                //set a error handler to catch not authorised 401's
                //http://stackoverflow.com/questions/6150514/global-error-handler-for-backbone-js-ajax-requests
                options || (options = {});
                this.bind("error", this.defaultErrorHandler);
                this.init && this.init(attributes, options);



                // Hook into ajax prefilter
                //send access token as header if available
                var that = this;
                $.ajaxPrefilter( function( options, originalOptions, jqXHR ) {

                    if (!options.crossDomain) {
                        options.crossDomain = true;
                    }

                    //use local access token if available
                    var access_token = localStorage.getItem("X-Lemonstand-Api-Token");
                    if(access_token){
                        jqXHR.setRequestHeader('X-Lemonstand-Api-Token', access_token);
                    }

                });

            },
            defaultErrorHandler: function(model, error) {
                if (error.status == 401 || error.status == 403) {
                    Backbone.history.navigate('#login', true);
                }
            },
            login: function(creds) {
                this.save({username: $('#username').val(), password: $('#password').val()}, {
                    success: function (response) {
                        var data = response.toJSON();
                        localStorage.setItem("X-Lemonstand-Api-Token",data.access_token);
                        Backbone.history.navigate('#', true);
                    }
                });
            },
            logout: function() {
                // Do a DELETE  clear the clientside data

                var that = this;
                this.destroy({
                    success: function (model, resp) {

                        //clear model
                        model.clear();
                        //delete local stored token
                        localStorage.removeItem('X-Lemonstand-Api-Token');
                        //send to login screen
                        Backbone.history.navigate('#login', true);



                    }
                });
            }


        });

    return new Customer();


});

damanic avatar Mar 21 '14 01:03 damanic

I included the model from your example code on the SpecRunner.html page, and calling fetch did indeed call through to the (mock) Backbone.sync.

How did you determine that ajax calls are not happening when dualsync is installed? Are you looking at the chrome inspector network requests?

nilbus avatar Mar 21 '14 03:03 nilbus

Sorry for delay, I'm juggling some other projects at the moment. When I get back onto this one I will look at it again and get you more information.

I was looking at chrome yes, there were no network requests with dual storage on, but fine when off.

damanic avatar Mar 24 '14 10:03 damanic

Hello,

I believe there's a bug here, because when I try to fetch a model that is not attached to a collection and doesn't have an id I get an exception because Store.find returns null and then my own parse function tries to access it. (This isn't a problem with a collection, because then Store.findAll simply reads the ids from localStorage and sets them appropriately.) When I explicitly set the id field (or whatever idAttribute says) everything works fine.

Can someone confirm this behavior and chime in on whether it's intended or not? My gut feeling says that it's not, because embedding the id in the code is not a robust solution...

If this is indeed a problem, I would suggest the following in Store.find, right after entry:

if (!model.id && !model.collection && this.records.length === 1) {
  model.set(model.idAttribute, this.records[0], { silent: true });
}

Or in CoffeeScript:

if not model.id and not model.collection and @records.length is 1
  model.set model.idAttribute, @records[0], silent: true

This fixes the problem for me at least.

Thoughts?

elad avatar May 11 '14 22:05 elad

Also, to really make this work we need an equivalent of dirtyModels, destroyedModelIds, and syncDirtyAndDestroyed for models.

Since we're talking about a single model, it can only be clean, dirty, or destroyed, and we always know its id, so maybe the API should be:

  • isDirtyOrDestroyed returns 'dirty', 'destroyed', or null
  • syncDirtyOrDestroyed checks for what is required and either calls save or destroy

If this makes sense, then perhaps it's also wise to rework the API in general to have more consistent naming - but we can discuss that elsewhere.

elad avatar May 11 '14 23:05 elad

Hey @nilbus, what's the status on this and other (async, IndexedDB) changes?

If you're still interested, I could submit new pull requests with tests for some of the issues.

elad avatar Oct 06 '14 16:10 elad

Unfortunately I'm having to take a hiatus on this project until early 2015 because of something more urgent that came up that is taking my free time.

Last I recall, I was trying to detemine if an issue I found in the async branch when rewriting the tests was legitimate or not. I didn't come to any conclusion before I stopped.

Sorry I can't be more help right now.

nilbus avatar Oct 06 '14 16:10 nilbus

Hi again @nilbus, I'd like to help more with this project, even as a contributor if you're interested. (I'm biased: I use it in a real-world environment. ;)

What's on your todo list? There was the async branch, the tests rewrite using proper Backbone models/collections and Mocha, and the multiple backend support. If you point me in the right direction in terms of priorities and problems you've encountered I'll look into things. I think it would be unfortunate to let dualStorage freeze for a few months...

elad avatar Nov 02 '14 14:11 elad

@elad Thank you, and please excuse the delay. I'll let you know shortly what I had been working on.

nilbus avatar Nov 10 '14 15:11 nilbus

Hi @nilbus, probably you remember me. I'm working on a similar project inspired by DualStorage called BackboneIDBDualStorage that is an exactly porting of DualStorage to IndexedDB. I'm happy to share my experience with @elad and you, if possible. I've touched the limits of the current architecture and I can help with the v2.0.

SonoIo avatar Nov 10 '14 15:11 SonoIo

Hi @SonoIo , let's continue this discussion in #73

nilbus avatar Nov 10 '14 15:11 nilbus

Would really love to see syncDirtyOrDestroyed() for models without collections. Everything @elad suggested in this comment, from above, is bang on.

jmeit avatar Jan 20 '17 03:01 jmeit

My priorities are elsewhere. I won't have time to author that change, but I'd be willing to review a pull request and try to get that in.

nilbus avatar Jan 20 '17 04:01 nilbus

what's the status

@elad To answer your earlier question from forever ago, the async/indexedDB branch got pretty hard to read with the async implementation. I started questioning whether it was worth it to make that change, given that there are other similar async indexedDB libraries out there already. That's where I left off.

Do you still use this in production? I'm not very active here recently, and the project could indeed use a new maintainer that actually uses the library. I do not.

nilbus avatar Jan 20 '17 04:01 nilbus

@nilbus unfortunately, no, I no longer use Backbone in production. :/

elad avatar Jan 22 '17 09:01 elad

No worries. Thanks for responding. :-)

nilbus avatar Jan 23 '17 00:01 nilbus