Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating nested models with backboneJS + backbone-relational + requireJS

I am new to BackboneJS and I am stuck with nested relations using Backbone-relational Model with RequireJS -I think I runned into circular issues. Any help will be highly appreciated!

I have the following model and collection:

 /* Module Model at models/module*/
define([
'jquery', 
'underscore', 
'backbone',
'backboneRelational',
], function($, _, Backbone) {

    var ModuleModel = Backbone.RelationalModel.extend({

        urlRoot: 'api/module',
        _radius: 50,
        relations: [{
            type: Backbone.HasMany,
            key: 'children',
            relatedModel: 'ModuleModel',
            collectionType: 'ModuleCollection',
            reverseRelation: {
                key: 'parent_id',
                includeInJSON: 'id' 
            }
        }],
        url: function() {
            return this.id? 'api/module/' + this.id : 'api/module';
        }
    });
    return ModuleModel;
});

/* Module Collection */
define([
'jquery',
'underscore', 
'backbone', 
'models/module'
], function($, _, Backbone, ModuleModel) {

    var ModuleCollection = Backbone.Collection.extend({

        model: ModuleModel,
        url: 'api/modules'
    });

    return ModuleCollection;
});

When I initialize the object ModuleModel, it throws the following error:

Relation=child; no model, key or relatedModel (function (){ parent.apply(this, arguments); }, "children", undefined)

Could you point me to the right direction?

like image 472
user1305990 Avatar asked Apr 01 '12 07:04

user1305990


3 Answers

This looks like a scoping issue. During initialization of ModuleModel it wants to create a hasMany relation with itself, but it can't find itself and it will give you grief in form of said error:

http://jsfiddle.net/yNLbq

Once the object is reachable from the current scope things start to work out:

http://jsfiddle.net/jDw5e

A possible solution would be to give models and collection a namespace for themselves which can be reached from the current scope.

Hope this helps.

like image 61
jp10k Avatar answered Sep 24 '22 17:09

jp10k


I came across this problem from here: RequireJS + BackboneRelational + Self-Referential. He seems to have inherited some of his problems from this thread so I thought I might add my dime.

First, since you're using RequireJS, there are no global variables. You can't simply supply the name of the object, you need to supply actual object references for relatedModel and collectionType.

Your trickiest issue is that ModuleModel's relatedModel is actually ModuleModel itself, which won't be defined when you assign it to relatedModel (using the AMD model). You have to defer assignment until after ModuleModel is assigned.

Finally, you need to resolve the circular reference. dokkaebi is on the right track when he suggests using exports, but his implementation actually misuses exports. When exporting, attach the object directly to exports as he suggests, but when you import it you need to reference the module to use it, not exports.

This should work:

ModuleModel.js

define(['exports', 'ModuleCollection'], function (exports, Module) {
    'use strict';

    var ModuleModel = Backbone.RelationalModel.extend({
        urlRoot: 'api/module',
        _radius: 50,
        relations: [{
            type: Backbone.HasMany,
            key: 'children',
            // ModuleModel is undefined; this line is useless
            // relatedModel: ModuleModel,
            // no globals in require; must use imported obj ref
            collectionType: Module.Collection,
            reverseRelation: {
                key: 'parent_id',
                includeInJSON: 'id' 
            }
        }],
        url: function() {
            return this.id? 'api/module/' + this.id : 'api/module';
        }
    });

    // Now that `ModuleModel` is defined, we can supply a valid object reference:
    ModuleModel.prototype.relations[0].relatedModel = ModuleModel;

    // Attach `Model` to `exports` so an obj ref can be obtained elsewhere
    exports.Model = ModuleModel;
});

ModuleCollection.js

define(['exports', 'ModuleModel'], function(exports, Module) {
    'use strict';

    var ModuleCollection = Backbone.Collection.extend({
        // must reference the imported Model
        model: Module.Model,
        url: 'data.php' // <-- or wherever
    });

    // Attach `Collection` to `exports` so an obj ref can be obtained elsewhere
    exports.Collection = ModuleCollection;
});

Main.js

define(['ModuleCollection'], function(Module) {
    'use strict';

    var modules = new Module.Collection();
    modules.fetch().done(function() {
      modules.each(function(model) {
        console.log(model);
      });
    });

});
like image 41
Chris Camaratta Avatar answered Sep 23 '22 17:09

Chris Camaratta


From a comment in backbone-relational.js v0.5.0 (line 375):
// 'exports' should be the global object where 'relatedModel' can be found on if given as a string.

If you require the special 'exports' value as a dependency in your define call, and then place your module onto the exports object before you return, then you can reference that module as a string or as a member of exports.

in ModuleModel.js:

define(['exports', 'use!backbone', 'use!backbone-relational'], function(exports, Backbone) {
  var ModuleModel = Backbone.RelationalModel.extend({
    relations: [
      {
        type: Backbone.HasMany,
        key: 'groups',
        relatedModel: 'ModuleModel',
        collectionType: 'ModuleCollection'
      }
    ]
  });
  exports.ModuleModel = ModuleModel;
  return ModuleModel;
});

and in ModuleCollection.js:

define(['exports', 'use!backbone'], function(exports, Backbone) {
  var ModuleCollection = Backbone.RelationalModel.extend({
    url: '/api/v1/module/';
    model: exports.ModuleModel;
  });
  exports.ModuleCollection = ModuleCollection;
  return ModuleCollection;
});
like image 38
dokkaebi Avatar answered Sep 21 '22 17:09

dokkaebi