Backbone-relational icon indicating copy to clipboard operation
Backbone-relational copied to clipboard

Many to many for single model without linking model

Open ghost opened this issue 13 years ago • 12 comments

I'm mapping out a Twitter like social graph with non-mutual followers/following relations. The use of a linking model makes sense in the Person/Job/Company example, but only adds overhead in the case of many-to-many for the same model on both sides. I find myself wanting to write a relation like

User = Backbone.RelationalModel.extend({
    relations: [{
        type: 'HasMany',
        key: 'following',
        relatedModel: 'User',
        reverseRelation: {
            type: 'HasMany',
            key: 'followers'
        }
    }]
});

Unless there is reason for this, I'd much like this feature! (or might even implement it myself)

ghost avatar Sep 21 '11 11:09 ghost

Well, the reason it isn't in there right now is that it's quite complicated to get this 'right', especially with regards to all the events flying around (which is already complicated as it is with just HasOne and HasMany relations). I've thought about it before, but didn't get to an intuitive / simple way to implement this. So I haven't until now; I'd rather have no implementation than a broken/buggy one.

I do have some ideas for an implementation, but it's not really fleshed out as of yet. I think it would basically have to be a new Backbone.Relation type (Backbone.ManyToMany?), which proxies two HasMany relations. This relation should be responsible for keeping both HasMany relations in sync, and relaying events to the other side of the relation. It could also allow through accessors in a manner similar to Django/Rails?

PaulUithol avatar Oct 21 '11 14:10 PaulUithol

I think this issue is pertinent to what I'm trying to do as well.

Basically I'm trying to create a simple file system which has a root folder which can then contain other folders or files, so I would need to allow a folder to folder relationship, with the added complication of having another model for files as well.

If I'm conceptualising this correctly I basically have the same problem that this functionality doesn't exist, yeah?

Completely understand why though, it's pretty difficult sounding coding problem :-)

Frobitz avatar Oct 24 '11 12:10 Frobitz

i have the same problum

nttdocomo avatar Mar 14 '12 08:03 nttdocomo

This would be brilliant for me. As it is now I create client-side through-models that have no correspondence on the server side, just to be able to do many2many. A django/rails equivalent behind-the-scens handling of those relations would be great!

ulmus avatar Apr 14 '12 14:04 ulmus

I'm currently trying to solve this problem in my app and the conclusion I came to is that you can't really avoid not having a linking model.

In our app we have meeting models which has an embedded invitees collection and a function that returns an attributes() collection, both of which consists of a users collection with user, with the additional attributes of rsvp_status and inviter_ids (the id's of users that did the inviting). After trying this approach, we learned that the moment we load up multiple meetings we have collisions with these two additional attributes because the model is relational, but these two attributes aren't. Our approach now is to change invitees to an invites collection of invite models that has the user as a relational property.

The JSON was:

{
  meeting: {
    // other properties
    invitees: [{
      id: 123,
      name: 'John Doe',
      rsvp_status: 'attending',
      inviter_ids: ['456', '789']   
    },{
      id: 456,
      name: 'Jane Doe',
      rsvp_status: 'not_replied',
      inviter_ids: ['789']           
    }]
  }
}

And we changed it to:

{
  meeting: {
    // other properties
    invites: [{
      user: {
        id: 123,
        name: 'John Doe',
      },
      rsvp_status: 'attending',
      inviter_ids: ['456', '789']   
    },{
      user: {
        id: 456,
        name: 'Jane Doe',
      },
      rsvp_status: 'not_replied',
      inviter_ids: ['789']           
    }]
  }
}

The latter approach contains an embedded collection, which we create and bind change:invites events to to repopulate/reset the invitees and attendees collections. This way the meeting contains an embedded invite collection, which itself contains relational attributes referencing the user that received the invite and the users that did the inviting.

With this in mind, I'd love to see support for initializing embedded collections in the models relations property, especially if I can define sieves like in Backbone.Subset, where the sieve is a function that calculates whether someone should or should not be part of the collection. In my use case above, I'd have a relational invitees collection whose members are determined by the users defined in the non-relational invites collection. I'd also have a relational attendees collection whose members are those invitees whose invite.get('rsvp_status') is equal to attending

It seems that using the properties of an embedded "many to many linking" collection like invites as a sieve to fill collection representing relations is one approach. It should be doable in a very "functional" way, where Backbone-Relational takes a function that returns the result of a truth test as a property in the relations objects.

I imagine something like this:

Meeting = Backbone.RelationalModel.extend({

  // stuff

  relations: [{
                  type: Backbone.HasMany,
                  key: 'invitees',
                  relatedModel: User,
                  collectionType: UserList,
                  sieve: this.resetInvitees,
                  reverseRelation: {
                    key: 'meetings',
                    relatedModel: Meeting,
                    includeInJSON: false
                  }
                },{
                  type: Backbone.HasMany,
                  key: 'attendees',
                  relatedModel: User,
                  collectionType: UserList,
                  sieve: this.resetAttendees,
                  reverseRelation: {
                    key: 'meetings',
                    relatedModel: Meeting,
                    includeInJSON: false
                  }
                }],

  embedded: [{
                     type: Backbone.HasMany,
                     key: 'invites',
                     relatedModel: Invite,
                     collectionType: InviteList
                   }],

  resetInvitees: function() {
    var invitees = [];
    this.invites.each(function(invite){
      var user = invite.get('user');
      invitees.push(user);
    })
    return invitees;
  },

  resetAttendees: function() {
    var attendee = [];
    this.invites.each(function(invite){
      var user = invite.get('user');
      if (user.get('rsvp_status') === 'attending') {
        attendees.push(user);
      }

    })
    return attendees;
  },

  // more stuff

});

Basically, in the above approach I use two function sieves to calculate which models from the embedded invites linking collection to include in the relational collections invitees and attendees

What I am not sure how to handle is the reverse relationships since the reverse relationships is not a property with a single meeting model, but a one meeting model in a collection of meeting models, which is the reason I defined the reverseRelation like so:

reverseRelation: {
  key: 'meetings',
  relatedModel: Meeting,
  includeInJSON: false
}

Any thoughts on this approach? Take a look at the Backbone.Subset plugin for a better understanding of what I have in mind. https://github.com/masylum/Backbone.Subset

andrewdeandrade avatar Apr 17 '12 00:04 andrewdeandrade

@PaulUithol I'd love to here more about the Backbone.ManyToMany relationship approach you mentioned.

andrewdeandrade avatar Apr 17 '12 00:04 andrewdeandrade

@emsten in your snippet above, where do you include the data that determines whether or not a model from collection A is linked to a model from collection B? Basically, using my case above, if Users can be invited to many meetings and meetings can contain many users, how do you determine which meetings a user is part of and which users are part of a meeting? The snippet shows that a many-to-many relationship between models of type A and type B are possible, but now how it is determined.

andrewdeandrade avatar Apr 17 '12 00:04 andrewdeandrade

if there would be a many_many relation like here http://doc.silverstripe.org/sapphire/en/topics/datamodel#relations you were my hero!

joernroeder avatar May 03 '12 00:05 joernroeder

Any updates on supporting this use case?

datiecher avatar Mar 19 '13 18:03 datiecher

This feature would be dreamy. Any movement?

ppalladino avatar May 22 '13 16:05 ppalladino

I tried adding support for a HasManyToMany relationship type today. It worked surprisingly well with very little effort. I'm probably missing some of the edge cases that @PaulUithol hinted at above. Does anyone have any insights into what issues I should be looking for?

col avatar Jul 10 '13 11:07 col

Probably the best way forward would be for the community to come up with common patterns of handling this problem outside of Backbone-relational. As those patterns get refined and gain consensus, then parts of them could strategically be moved into Backbone-relational. Does anyone have good patterns to share on this topic? (They'd make great fodder for blogs!)

mcordingley avatar Feb 11 '14 15:02 mcordingley