PaulUithol/Backbone-relational

Experimenting

pgherveou opened this issue · 8 comments

Hi I am experimenting on backbone relational, (looks great !)
I create the following in console

var Membership = Backbone.RelationalModel.extend({
    urlRoot: '/membership',
});

var User = Backbone.RelationalModel.extend({
    urlRoot: '/user',
    relations: [
        {
            type: Backbone.HasMany,
            key: 'memberships',
            relatedModel: 'Membership',
            collectionType: 'MembershipCollection',
            reverseRelation: {
                key: 'user'
            }
        }
    ]
});

var UserCollection = Backbone.Collection.extend({model:User});
var MembershipCollection = Backbone.Collection.extend({model:Membership});

// create a membership from a json hash
member = new Membership({
                "id": 4,
                "user": {
                    "id": 7,
                    "username": "paul",
                    "email": "paul@mail.com"
                }
            })

If I type member.get('user') it returns me the user object, I was expecting to receive a Model instance of it
Can you let me know what's wrong with this piece of code ?

Thks

Actually it works if I reverse the relation writting a hasOne on Membership, but is there a way to make it work both side ?

Yeah, you've stumbled over one nasty issue that I don't know how to solve (not sure it can be solved actually..).

If you define relations on a model (like you do on User), but don't actually create an instance of that model, the model's constructor function will never run, so initializeRelations will never get called, and the reverseRelation will never be set on Membership.

You can solve this by either creating a dummy user (new User()), or defining the complete relation (including the reverseRelation) on Membership.

Might be something to include as a warning in the docs..

I have to dig into your code to get a better understanding of the relation binding,
but one stupid question why can't it be initialized from the User constructor ?

It works writing the relation the other way, I also had a many to many relationship I build with two hasOne to get this working, it does not do any harm does it? You are right I think it worth adding this tips to the doc

Thks for this excellent plugin anyway !

It is. But if you never actually create a user (by calling new User()), the User constructor will never run.

I'm not sure if its intended process, but I've worked around this issue by not using reverseRelation at all, and defining each side of the relation in each model:

var Membership = Backbone.RelationalModel.extend({
    urlRoot: '/membership',
    relations: [
      {
        type: Backbone.HasOne,
        key: 'user',
        relatedModel: 'User'
      }
    ]
});

var User = Backbone.RelationalModel.extend({
    urlRoot: '/user',
    relations: [
      {
        type: Backbone.HasMany,
        key: 'memberships',
        relatedModel: 'Membership',
        collectionType: 'MembershipCollection',
      }
    ]
});

This seems to have cleaned up this problem for me - and it feels a little more elegant as well.

Using "reverseRelation" on both sides got me a bunch of warnings that relationships had already been defined.

Although, just bootstrapping up an instance of each model on app initialization, while a bit of a hack, isn't too onerous.

Actually, I think that's actually the nicest & most reliable solution :)

I just noticed that if you define relation as Edward suggest then in the above example
the relationship can't be access like that :

Backbone.Relational.store.find(User, 7).get("memberships").size() == 1 // return false

here is the full example runnable in console

window.Membership = Backbone.RelationalModel.extend({
    urlRoot: '/membership',
    relations: [
      {
        type: Backbone.HasOne,
        key: 'user',
        relatedModel: 'User'
      }
    ]
});

window.User = Backbone.RelationalModel.extend({
    urlRoot: '/user',
    relations: [
      {
        type: Backbone.HasMany,
        key: 'memberships',
        relatedModel: 'Membership',
        collectionType: 'MembershipCollection',
      }
    ]
});


window.UserCollection = Backbone.Collection.extend({model:User});
window.MembershipCollection = Backbone.Collection.extend({model:Membership});


window.member = new Membership({
                "id": 4,
                "user": {
                    "id": 7,
                    "username": "paul",
                    "email": "paul@mail.com"
                }
            })

console.log("memberships nb: " + Backbone.Relational.store.find(User, 7).get("memberships").size());

Yeah, you're right; didn't realize that downside on time.

If you don't specify a reverseRelation, the relation can't know what attribute to monitor (and populate) on the opposing model; so in that case, you'll need to explicitly specify the other side of the relation, which kind of defeats the purpose.

Yet another option is to fully specify the relation + reverseRelation on both models; this way, they'll be set up the first time an instance of either of model is created. The downside to this is that you'll get a couple of warnings about duplicate relations being defined (and the code duplication of course).