Creating nested models with backboneJS + backbone-

2019-01-22 16:00发布

问题:

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?

回答1:

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.



回答2:

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

});


回答3:

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


回答4:

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.