Creating nested models with backboneJS + backbone-relational + requireJS
Asked Answered
K

4

10

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?

Koodoo answered 1/4, 2012 at 7:33 Comment(1)
Similar to #9226140Calorifacient
P
4

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.

Peluso answered 9/5, 2012 at 8:47 Comment(0)
A
3

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);
      });
    });

});
Afflatus answered 9/6, 2013 at 1:6 Comment(0)
S
2

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;
});
Sussna answered 8/6, 2012 at 22:26 Comment(3)
exports is not something you define; it is treated specially by require. Requirejs creates a dictionary and passes it through to any definitions that require 'exports' so that you can share values between define calls. You needn't create a file nor do anything special -- just use the magical name 'exports' as one of your dependencies.Sussna
Please can you say me which module should I instantiate? both or just one, and if just one, which between ModuleCollection and ModuleModel?Murat
Using the code I posted, both ModuleModel and ModuleCollection should work as expected. Elsewhere in your code, you might do something like require(['ModuleCollection', 'ModuleModel'], function(ModuleCollection, ModuleModel) { myModules = new ModuleCollection(); myModel = new ModuleModel({ /* some values */ }); myModules.add(myModel); }. It might help to see what you're trying to do -- if this doesn't work as expected, you could open a new question and link to it here.Sussna
C
2

I ran into the same problem a while back and followed the approach used by Andrew Ferk for his question: Backbone-relational submodels with RequireJS. The problem arises because you're defining models as Require modules so they don't exist on the global scope, where backbone-relational can look for them. Instead of using global scope (beats the purpose of Require) or exports (bit tricky with the relations), you can define a scope for your models and tell backbone-relational to look for models in it, with addModelScope().

//modelStore.js - A scope in which backbone-relational will search for models
//Defined separately so you can access 'modelStore' directly for your models instead of requiring 'app' every time.
define(['app'], function(app) {
    app.modelStore  = {};
    Backbone.Relational.store.addModelScope(app.modelStore);
    return app.modelStore;
}

By the way you should shim your Backbone dependency and not need to require jQuery and Underscore for it.

//ModuleModel (The ModuleModel module. Nice.)
define(['modelStore', 'backbone', 'backboneRelational'], function(modelStore, Backbone {
    modelStore.ModuleModel = Backbone.RelationalModel.extend({
        relations: [
        {
            type: Backbone.HasMany,
            key: 'groups',
            relatedModel: 'ModuleModel', //Not modelStore.ModuleModel
            collectionType: 'ModuleCollection'
        }
        ]
    });
    return modelStore.ModuleModel;
});

Kinda late for this now but hope it helps others.

Corbett answered 19/4, 2014 at 17:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.