bricker
bricker

Reputation: 8941

Class inheritance, and requiring subclasses from different files with Coffeescript

I am trying to organize my code a bit but having issues with undefined superclasses. I'm hoping it's just a load path problem, but I can't figure it out. I am running this with:

coffee rooms.coffee

rooms.coffee

Room = require('./rooms/room')

module.exports = class Rooms extends Object
    constructor: ->
        @

rooms/room.coffee

module.exports = class Room
    @Active: require('./active')

    constructor: (@id) ->
        @users = {}

rooms/active.coffee

Room = require('./room')

console.log Room #=> {}

module.exports = class Active extends Room
    constructor: (@id) ->
        @type = "Active"
        super

And if I try to do new Active, I get the following error:

TypeError: Cannot read property 'constructor' of undefined

Active's super is listed as undefined:

[Function: Active] __super__: undefined

Why Room undefined? (or rather, just an empty Object?)

Update

This was caused by a circular dependency as many people below pointed out. I ended up just putting the subclass definitions right inside of the baseclass definition, rather than try to keep them in separate files. Something like this:

class Room
  constructor: ->
    # ...

  class @Active extends Room
    constructor: ->
      # ...

  class @Inactive extends Room
    constructor: ->
      # ...

active   = new Room.Active
inactive = new Room.Inactive

Upvotes: 3

Views: 1055

Answers (3)

Jonathan Ong
Jonathan Ong

Reputation: 20315

@Active: require('./active') remove this, or move this somewhere else. You have a circular dependency. When you define room.coffee, you're telling it to require active.coffee before it can finish defining Room, and active.coffee requires Room, so Room will be undefined when active.coffee is required.

You should be able to just add a Room::Active = require('./active') at the end, but then I ask why you even need this in the first place o.O

Upvotes: 0

David Weldon
David Weldon

Reputation: 64312

This is a case where simplifying the code down to its most primitive parts (while still seeing errors) is illuminating. If we remove the requires and strip out most of the code, we can get a structure like this:

class Room
  @foo = "bar"

class Active extends Room

console.log Room.foo

which prints: bar as expected.

So now lets try getting a little closer to the original example:

class Room
  @foo = Active

class Active extends Room

console.log Room.foo

This prints undefined because Active was not defined when Room.foo was defined.

Finally, let's look at the case were the definitions are reversed:

class Active extends Room

class Room
  @foo = Active

console.log Room.foo

This throws an error because it isn't possible to extend undefined.

The last two cases represent changing the require order in your original example. Having the definition of a base class depend on its subclass should cause your OOP alarm bells to start ringing! :)

There may be a way to change the definitions slightly to make this work, but code with these kinds of mutual dependencies tends to be unmaintainable at best. I'd recommend figuring out a way to completely decouple the classes.

Upvotes: 2

phenomnomnominal
phenomnomnominal

Reputation: 5515

I can't be sure about this but I may have had a similar problem - it might be to do with the fact that the file room/active.coffee is before room/room.coffee alphabetically, so when active.coffee file is loaded the Active class is first looked at, room.coffee and Room haven't been yet, so the superclass can't be found.

I got around this by using the -j operator at compile time, so while your .coffee files are nice and organised, they compile into a single .js file, which has other advantages as well.

Upvotes: 0

Related Questions