2

I've run into a problem that may have to do with my lack of understanding of the use of exports / RequireJS for circular dependencies.

I'm getting the error relatedModel does not inherit from Backbone.RelationalModel.

On to the code (in CoffeeScript; I hope that's alright)...

I have two Backbone Models / RequireJS modules, FooModel and BarModel:

FooModel:

define (require) ->
  Backbone = require 'backbone'
  BarModel = require 'models/bar'

  FooModel = Backbone.RelationalModel.extend
    relations: [
      type: Backbone.HasMany
      key: 'theBars'
      relatedModel: BarModel  # <-- this is where the BB Relational error is coming from
    ]

  return FooModel

BarModel:

define (require, exports) ->
  Backbone = require 'backbone'
  FooCollection = require 'collections/foos'

  BarModel = Backbone.RelationalModel.extend
    someFunction: ->
      # uses FooCollection
      # I've tried moving the require in here and getting rid of exports

  exports.BarModel = BarModel
  return BarModel # I've tried with and without this line, but CS just returns the last line anyway so removing it is functionally the same

I have also tried:

  1. Extending FooModel from Backbone.Model instead of Backbone.RelationalModel and creating the theBars collection myself (in parse and in custom function). (BarModel has a HasOne relation of a another model, so I need it to still be a RelationalModel.

Is this possibly a problem with the way exports works? As far as I understand, exports just provides an object to hang module objects on so the modules are accessible elsewhere. Is the error occurring because the BarModel isn't actually a Backbone Model at the point in the FooModel code where I define relations?

Update

I seem to have solved my issue, although I'm unsure how. Can't say I'm pleased about not understanding why it's working, but I sure am pleased that it is working. Also see my comment about _.memoize below in the BarModel code.

(Before I got the code below to work, I created a workaround whereby I created the associated collection in FooModel's parse function and exported BarModel. However, the response of require 'collections/foos' returned an object like so: {FooCollection: <Backbone.Collection Object>}, i.e. it was unexpectedly wrapped in another object.)

Here's the updated code:

FooModel:

define (require) ->
  Backbone = require 'backbone'
  BarModel = require 'models/bar'
  BarCollection = require 'collections/bars'

  FooModel = Backbone.RelationalModel.extend
    relations: [
      type: Backbone.HasMany
      key: 'theBars'
      relatedModel: BarModel
      collectionType: BarCollection
    ]

  return FooModel

BarModel:

define (require, exports) ->
  Backbone = require 'backbone'

  BarModel = Backbone.RelationalModel.extend
    someFunction: -> #this actually used to use _.memoize (sorry for the incomplete code), so maybe it would have tried to run the function argument immediately? 
      # uses FooCollection
      FooCollection = require 'collections/foos'

  return AttributeModel
mnoble01
  • 579
  • 5
  • 16

1 Answers1

0

Your BarModel requires 'collections/foos', correct? And I'm guessing (since there's no code for FooCollection) that the collection requires 'models/foo', because a collection needs to define it's model right? Finally, I can see from the code above that your foo model requires 'models/bar'.

In other words foos needs foo needs bar needs foos needs foo needs bar needs ...

No matter how Require decides to order that, one of those three has to be loaded before the others, which will give you problems like the one you are having.

The solution is to not load one of those three until after all three modules are loaded. For instance, what if you change:

define (require, exports) ->
    Backbone = require 'backbone'
    FooCollection = require 'collections/foos'
    BarModel = Backbone.RelationalModel.extend
        someFunction: ->
            # uses FooCollection

to:

define (require, exports) ->
    Backbone = require 'backbone'
    BarModel = Backbone.RelationalModel.extend
        someFunction: ->
            FooCollection = require 'collections/foos'
            # uses FooCollection

Now BarModel can load, and while someFunction is defined, it is not actually run yet, so it won't require foos and create a circular dependency. Later on, after everything is loaded and some code invokes someFunction, foos will already have had a chance to load, and the require should work.

Now I say should work because of your comment:

# I've tried moving the require in here and getting rid of exports

Again, I have to guess since I can't see your code, but I'd imagine that what happened is that nothing else depended on foos, so it never got loaded. In order for the require of foos to work synchronously inside someFunction, the foos module has to have previously been loaded.

To fix this you just need to add a dependency on foos ... only this time not in any module that requires foos (or any that require a module that requires foos, or ...).

Hope that helps.

machineghost
  • 33,529
  • 30
  • 159
  • 234
  • Yes, the FooCollection requires `models/foo` in order to define its model. Also, I agree moving the require into `someFunction` *should* work, but as I said, I already tried that. – mnoble01 Mar 19 '13 at 16:06
  • I got this working a few days ago. You can check out my update to the question above. I have a suspicion my problems had to do with `_.memoize`. – mnoble01 Mar 19 '13 at 16:26
  • I don't think `_.memoize` could have caused it; `memoize` just wraps the provided function with a function that caches the return values, and that wouldn't prevent require from returning a module in any way. As I said in my answer, the way to make a require call not return a module is to: A) have a circular dependency, or B) use callback-less form of require on a module that hasn't been loaded yet. You must have either added a dependency to make the module load or removed (another, different) circular dependency - that's the only way you could have fixed whatever error you had before. – machineghost Mar 19 '13 at 16:39