3

I successfully implemented loading and showing relations with 'Backbone Relational' from an API I created. I get how things work by trial and error. I do think the docs are lacking some clarity though since it took a lot of time to figure out how things work. Especially on how to map things to the API I think the docs are lacking a bit.

Problem
Adding a bookmark works, it's the editing and deletion that don't work. The PUT becomes a POST and the DELETE simply doesn't fire at all. When I set an id to the model hardcoded it does work. So the id is missing which makes sense for the PUT becoming a POST.

The problem seems to be that the id doesn't hold an actual id, but a collection. The view where the problem occurs does not requires the BookmarkBinding, it's used somewhere else. Simply the fact that it has Bookmark as a relation makes the DELETE and PUT break.

BookmarkBinding model:

App.Model.BookmarkBinding = Backbone.RelationalModel.extend({
    defaults: {
        set_id: null,
        bookmark_id: null
    },
    relations: [{
        type: Backbone.HasOne,
        key: 'bookmark',
        relatedModel: 'App.Model.Bookmark',
        reverseRelation: {
            type: Backbone.HasOne,
            key: 'id'
        }
    }],
    urlRoot: 'http://api.testapi.com/api/v1/bookmark-bindings'
});

Bookmark model:

App.Model.Bookmark = Backbone.RelationalModel.extend({
    defaults: {
        url: 'undefined',
        description: 'undefined',
        visits: 0,
    },
    relations: [{
        type: Backbone.HasMany,
        key: 'termbindings',
        relatedModel: 'App.Model.TermBinding',
        reverseRelation: {
            key: 'bookmark_id',
        }
    }],
    urlRoot: 'http://api.testapi.com/api/v1/bookmarks'
});
sidneydobber
  • 2,790
  • 1
  • 23
  • 32
  • Why do you think this is related to BBR specifically? It doesn't override either `save` or `destroy` - and the only check in Backbone to determine what (if any, in the case of `destroy`) HTTP request is made is `model.isNew`, which merely checks if a model has an `id` set. Some runnable code (like a jsfiddle) might help a lot. – Paul Jun 10 '14 at 19:52
  • By the way, if you have suggestions about docs improvements, please let us know on github - people are using BBR with a wide variety of APIs, and examples for actual services (showing how to use keySource/keyDestination, etc) could be pretty helpful I think. – Paul Jun 10 '14 at 19:54
  • Hey Paul, thanks (bedankt) for your comment. It's no criticism on your library. The thing is I'm using BackboneJS for a while now, got the concepts down and I'm making an app with a lot of relations, which led me to your library. BBR was quite hard to grasp at first due to my experience with using relations in Eloquent ORM. The way BBR creates it's relations with key and foreign key was fuzzy for me, but I blame myself, not your library. I came a long way, until I bumped into the fact of having to use unique key, sourceKey and destinationKey. When I have got some time I will setup a Fiddle! – sidneydobber Jun 11 '14 at 06:50
  • Bedankt! Wasn't taking it as criticism, just wondering if this isn't a 'regular' Backbone issue (which would make it simpler to fix). And if you have suggestions for improving the syntax or the docs on some topic, please do let me know. I'm so used to it that I just don't notice the idiosyncrasies in it anymore... – Paul Jun 12 '14 at 00:21

2 Answers2

3

From Backbonejs.org

The default sync handler maps CRUD to RESTful HTTP methods like so:

create → POST   /collection
read → GET   /collection[/id]
update → PUT   /collection/id
delete → DELETE   /collection/id

Your question suggests that you're making an HTTP PUT request, and therefore a Backbone update. If you want to make an HTTP POST, use Backbone create. The PUT request maps onto update, and requires that an id be sent in the URL, which isn't happening according to your server log. If your're creating a new object, then most server-side frameworks such as Rails / Sinatra / Zend will create an id for the object

Another possible source of error is the keys that you chose for the relations, like you suspected.

A Bookmark has many BookmarkBindings, and it seems that Backbone-relational will store them in the field that you specify in BookmarkBindings.relations.reverseRelation.key, which is currently defined as 'id'. So the collection of related BookmarkBindings ids will to be stored on the same attribute as the Bookmark.id, creating a collision. Backbone.sync will send an undefined value to the server (which you see in your logs), because it finds a collection there instead of an integer.

First suggestion - You may not need a bidirectional relation, in which case drop it from the BookmarkBinding model.

Second suggestion - define the reverse relation on another key, so that it doesn't collide with Bookmark.id, such as BookmarkBindings.relations.reversRelation.key : 'binding_ids'

  • due disclosure - I've never used Backbone-relational.js, only Backbone.js.
jarede
  • 56
  • 3
  • I don't make the PUT request, Backbone decides the method based on whether there is an existing model. That is the whole problem. I already tried to drop the reverse relation, but that didn't work either. Thanks for reacting. I will give it some more research later on. – sidneydobber Jun 07 '14 at 10:49
2

The problem was that on editing or deleting the bookmark model, the bookmark binding model wanted to do it's work too since it is related too the bookmark from it's side. I already tried to remove the reverse relation which didn't prove to be a solution since in the other part of my application where I used the bookmark bindings things wouldn't work anymore.

Solution
I did end up removing the reverse relation (@jarede +1 for that!), but the crux was how to implement the foreign key to fetch relations from the API without a reverse relation. I ended up adding the keySource and keyDestination which made everything work out.

Sidenote
Backbone Ralational cannot handle identical foreign keys either, this gave me some problems too. This will make the lastly declared foreign key overwrite all the previous ones. This can be quite impractical since within an API it's not uncommon that model's are related to a column named id. So the idAttribute can be set with idAttribute: '_id' for example, but the foreign key has to be unique across your application.

BookmarkBinding model:

App.Model.BookmarkBinding = Backbone.RelationalModel.extend({
    defaults: {
        set_id: null,
        bookmark_id: null
    },
    relations: [{
        type: Backbone.HasOne,
        key: 'bookmark',
        keySource: 'id',
        keyDestination: 'bookmark',
        relatedModel: 'App.Model.Bookmark'
    }],
    urlRoot: 'http://api.testapi.com/api/v1/bookmark-bindings'
});
sidneydobber
  • 2,790
  • 1
  • 23
  • 32