Coffeescript class find or create mongo document

484 views Asked by At

I'm trying to write a coffeescript class that when constructing a new object from it, it checks to see if an ID was passed. If so, try to find a document matching and populate object from that. If no ID is passed, generate a new ID an create a new document. I'm using mongojs to connect to my mongodb. However when I create a new object from the TestObject class, it throws an error that the collection name needs to be a string. I set @collection as a string on that class so I console.log the @collection property and its undefined. Whats going on here and how can I make this work?

class MongoObject
    @collection
    constructor: (id) ->
        @_id = if typeof id is 'undefined' then require('node-uuid').v4() else id
        @db = require('mongojs') config.mongo_server, [@collection]

        @db[@collection].findOne
            _id: @_id,
            (error, story) ->
                # Matching document found. Import data from document
                if not error and story
                    for field, value of story
                        @[field] = value if field is not '_id'

                # Matching document not found. Creating new one
                if not error and not story
                    @db[@collection].save
                        id: @id

                # Error occured
                if error and not story
                    console.error error

                return

class TestObject extends MongoObject
    @collection = 'TestObjects'
    constructor: (id) ->
        super('TestObject')

Edit

Rereading my code its clear that its an issue with the constructor and @collection being undefined in MongoObject. Is there a better approach to doing this? I could create a setupDB method and call that in each class's constructor that extends MongoObject after the super call but not what I was hoping for.

Edit 2

I revised my code. However I'm now getting an error that constructor is undefined. When I looked at the compiled javascript its pointing to constructor; at the top of the MongoObject code. Oddly enough coffeescript didn't put var constructor; which is normally what happens. I've posted the translated javascript just for reference

Coffeescript

class MongoObject
    collection: undefined
    constructor: (id) ->
        @_id = if typeof id is 'undefined' then require('node-uuid').v4() else id
        @db = require('mongojs') config.mongo_server, [@collection]

        @db[@collection].findOne
            _id: @_id,
            (error, story) ->
                # Matching document found. Import data from document
                if not error and story
                    for field, value of story
                        @[field] = value if field is not '_id'

                # Matching document not found. Creating new one
                if not error and not story
                    @db[@collection].save
                        id: @id

                # Error occured
                if error and not story
                    console.error error

                return

class TestObject extends MongoObject
    collection = 'TestObjects'
    constructor: (id) ->
        super('TestObject')

Javascript

MongoObject = (function() {
  MongoObject.prototype.collection = void 0;

  function MongoObject(id) {
    this._id = typeof id === 'undefined' ? require('node-uuid').v4() : id;
    this.db = require('mongojs')(config.mongo_server, [this.collection]);
    this.db[this.collection].findOne({
      _id: this._id
    }, function(error, story) {
      var field, value;
      if (!error && story) {
        for (field in story) {
          value = story[field];
          if (field === !'_id') {
            this[field] = value;
          }
        }
      }
      if (!error && !story) {
        this.db[this.collection].save({
          id: this.id
        });
      }
      if (error && !story) {
        console.error(error);
      }
    });
  }

  return MongoObject;

})();

TestObject = (function(_super) {
  var collection;

  __extends(TestObject, _super);

  collection = 'TestObjects';

  function TestObject(id) {
    TestObject.__super__.constructor.call(this, 'TestObject');
  }

  return TestObject;

})(MongoObject);

Edit 3

Updated my code per my comments. Its saying that @constructor.collection is undefined in

@db[@constructor.collection].save
    id: @id

I assume its because its in the callback function of save. One step foward, two steps back.

Revised code

class MongoObject
    @collection
    constructor: (id) ->
        @_id = if typeof id is 'undefined' then require('node-uuid').v4() else id
        @db = require('mongojs') config.mongo_server, [@constructor.collection]

        @db[@constructor.collection].findOne
            _id: @_id,
            (error, story) ->
                # Matching document found. Import data from document
                if not error and story
                    for field, value of story
                        @[field] = value if field is not '_id'

                # Matching document not found. Creating new one
                if not error and not story
                    @db[@constructor.collection].save
                        id: @id

                # Error occured
                if error and not story
                    console.error error

                return

class TestObject extends MongoObject
    @collection: 'TestObjects'
    constructor: (id) ->
        super('TestObject')
3

There are 3 answers

0
Kylee On

This is the solution I ended up with

class MongoObject
    @collection
    constructor: (id) ->
        @_id = if typeof id is 'undefined' then require('node-uuid').v4() else id
        @db = require('mongojs') config.mongo_server, [@constructor.collection]

        @db[@constructor.collection].findOne
            _id: @_id,
            (error, doc) =>
                # Matching document found. Import data from document
                if not error and doc
                    console.log 'Document found. Updating.'
                    for field, value of doc
                        @[field] = value if field is not '_id'

                # Matching document not found. Creating new one
                if not error and not doc
                    console.log 'No document found. Creating.'
                    @db[@constructor.collection].save
                        _id: @_id

                # Error occured
                if error and not doc
                    console.error error

                return

class TestObject extends MongoObject
    @collection: 'TestObjects'
2
mu is too short On

I think you're confused about the meaning of @ at the class level. A simplified example should help, this CoffeeScript:

class B
    @p: 'b'

is the same as this JavaScript:

var B = (function() {
  function B() {}
  B.p = 'b';
  return B;
})();

So you can see that p is a class property that is directly attached to the C class/function. But when you're inside a method, such as constructor, @ refers to the instance so, in your case, @collection will be undefined because you're defining collection as a class property.

You probably want collection to be an instance property:

class MongoObject
    collection: undefined
    constructor: (id) ->
        # @collection is what you expect it to be in here

class TextObject extends MongoObject
    collection: 'TextObject'

Demo: http://jsfiddle.net/ambiguous/TzK5E/

Alternatively, you can keep collection as a class property and reference it through @constructor:

class MongoObject
    @collection: undefined
    constructor: (id) ->
        # @constructor.collection is what you expect it to be in here

class TextObject extends MongoObject
    @collection: 'TextObject'

Demo: http://jsfiddle.net/ambiguous/wLjz3/

0
WiredPrairie On

I believe you'll want to use slightly different syntax to reference the collection name:

class MongoObject
    @collection 
    constructor: (id) ->
       alert @constructor.collection


class TestObject extends MongoObject
    @collection = 'TestObjects'
    constructor: (id) ->
        super('TestObject')

t = new TestObject

Alerts "TestObjects"

The key is the use of @constructor.collection.