3

I want to create a Javascript class/object that allow me to have various method:

Model class

  • Model.all() » static method
  • Model.find() » static method
  • Model delete() » instance method
  • Model save() » instance method
  • Model.create() » static that returns a new Model instance

For static method I can define them using:

Model.staticMethod(){ method }

while for instance method is better to use:

function Model(){
    this.instanceMethod = function(){}    
}

and then create a new instance

or using prototype?

var m = function Model(){

}

m.prototype.method() = function() {
}

Now let's say that I want to create a new class based on Model, how to inherit not only its prototypes but also its static methods?

EDIT:

to avoid confusion this is more or less what I want to create:

http://activejs.org/activerecord/index.html and http://activejs.org/activerecord/ActiveRecord/Model/index.html

where I can define a new model using something like that

var User = ActiveRecord.create({
    username: '',
    password: '',
    post_count: 0,
    profile: ''
}

then create an instance

var jessica = User.create({
    username: "Jessica",
    password: "rabbit"
});

use instance methods like

jessica.save();

but also class methods:

User.findByUsername('Jessica');
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Matteo Pagliazzi
  • 5,140
  • 12
  • 49
  • 83
  • Yes you should use `prototype`, otherwise you create new function objects for every object you instantiate. – Esailija Jun 19 '12 at 09:05
  • possible duplicate of [Use of 'prototype' vs. 'this' in Javascript?](http://stackoverflow.com/questions/310870/use-of-prototype-vs-this-in-javascript) – Felix Kling Jun 19 '12 at 09:11
  • Regarding your last question, you probably have to iterate over the methods you added to `Model` and assign them to the new class as well. – Felix Kling Jun 19 '12 at 09:14
  • so not something referred to the whole class? because to me seems a bit strange to do var a = new Model() and then a.all since a represents a single istance it shouldn't be used to get all the models from the server (all() should do this) – Matteo Pagliazzi Jun 19 '12 at 09:16
  • 2
    There are no real static methods. Everything is an object and everything can have properties. If you assign `Model.all = ...`, then later you call `Model.all()`. `var a = new Model(); a.all()` would not even work. Or maybe you misunderstood my comment and I yours. – Felix Kling Jun 19 '12 at 09:27
  • yes the problem is how to get "static methods" (Model.all()) to get inherited by child object – Matteo Pagliazzi Jun 19 '12 at 09:31
  • Ah well, then this was not what I meant. I meant that you have to iterate over `Model`'s properties and assign each function to `NewModel` (like Florian is showing in his answer now). That's not really inheritance, but there is no other way afaik. – Felix Kling Jun 19 '12 at 09:32
  • ok it may be a great preformance issue? – Matteo Pagliazzi Jun 19 '12 at 09:38
  • No it's not a performance issue. It is only done once. – Esailija Jun 19 '12 at 09:58

4 Answers4

5
function Model() {}

// Methods in the instantiated object
Model.prototype = {
    constructor: Model,

    // Note that "delete" is a reserved word, so we need quotes
    'delete': function() {},

    save: function() {}
};

// Static methods
Model.all = function() {};

Model.find = function() {};

Model.create = function() {
    return new Model();

    // To be more generic, you can also:
    return new this();
};

When you use var InheritedModel = Object.create( Model );, it also inherits the static methods.

var InheritedModel = Object.create( Model );
!!InheritedModel.all // true

However, you can't do new InheritedModel(), because it's not a function, and using Object.create( InheritedModel ) won't give you the instance methods.

If you want to instantiate the inherited class using new, you need the following:

function InheritedModel() {}

InheritedModel.prototype = Object.create( Model.prototype );

// Copy all the static methods in the InheritedModel object
Object.keys( Model ).forEach( function( key ) {
    InheritedModel[ key ] = Model[ key ];
} );

Edit: after seeing your edit, here is the solution I'd recommend you:

function ActiveRecord( type, args ) {
    if ( type = 'users' ) {
        return new this.users();
    }
}

// Static method on ActiveRecord
ActiveRecord.create = function( type, args ) {
    return new ActiveRecord( type, args );
};

ActiveRecord.prototype = {
    constructor: ActiveRecord,

    // Instance method on ActiveRecord, you won't need it,
    // but your constructor does
    users: function( args ) {}
};

var Users = ActiveRecord.prototype.users;

Users.prototype = {
    constructor: Users,

    // Instance method on User's instance
    save: function() {}
}

// Static method on User
Users.create = function() {}
Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
  • how to inherit static methods with this? – Matteo Pagliazzi Jun 19 '12 at 09:13
  • @MatteoPagliazzi by using `Object.create` – Florian Margaine Jun 19 '12 at 09:23
  • @MatteoPagliazzi Edited to add another solution – Florian Margaine Jun 19 '12 at 09:30
  • this seems exactly what i need! only one question: this way each inherited model will create a new function for model.all it won't use the same like when using prototype right? this may cause big problems with performances or it's negligible? – Matteo Pagliazzi Jun 19 '12 at 09:34
  • @MatteoPagliazzi be careful, I edited the "create InheritedModel" stuff. And it will not create a prototype, it still uses `Model` methods in the prototype, not creating a new one. – Florian Margaine Jun 19 '12 at 09:39
  • see my edit to the question to see what i want to achieve. I'm a bit confused, in your answer using Object.create when calling a static method in the inherited model it will a copy of the method from the Model class not the same one. What I mean is that each models will not share the same all() static method from the Model class but each model will have its all() method and this couldn't bring some performance issues? The perfect solution would be to use the same method but each model passing to it different parameters – Matteo Pagliazzi Jun 19 '12 at 09:51
  • @Matteo: It's the same method. `Model.all === InheritedModel.all` will yield `true`. You are not creating a new method, you are simply creating a new reference to the existing one. – Felix Kling Jun 19 '12 at 10:16
  • @MatteoPagliazzi What is there is the final code, I think. Esailija is going to add an answer with his own view of how things should be though. We're talking about your question since an hour or so in SO chat ;o) – Florian Margaine Jun 19 '12 at 10:31
  • wow, thanks for the help! here function ActiveRecord( type, args ) { if ( type = 'users' ) { return new this.users(); } } ActiveRecord should be avalaible not only for users but for any Modle I'de like to create like posts, notes – Matteo Pagliazzi Jun 19 '12 at 11:03
4

Commented code is pseudo code. You can achieve the same by:

var modelInstanceMethods = {

    save: function() {
        /*
        insert into this.tableName blabla
        */
    },

    'delete': function() {
        /*
        delete from this.tableName blabla
        */
    }
};

var modelStatics = {

    create: function(obj) {
        return new this(obj);
    },

    all: function() {
        /*
        return select * from this.tableName.map( function( values ) {
            return new this(values);
        },this);
        */
    },

    find: function(id) {
        /*
        select * from this.tableName where id = id
        return new this(columnValues);
        */
    }


};

var ActiveRecord = {
    create: function( tableName, fields, methods ) {

        function Model( obj ) {
            this.tableName = tableName;
            this.fields = {};
            if( fields ) {
                for( var field in fields ) {
                    this.fields[field] = fields[field];
                }
            }

            if( obj ) {
                for( var field in obj ) {
                    this.fields[field] = obj[field];
                }       
            }
        }

        Model.tableName = tableName;
        Model.prototype = Object.create(modelInstanceMethods);
        Model.prototype.constructor = Model;

        for( var key in modelStatics ) {
            Model[key] = modelStatics[key];
        }

        if( methods ) {
            for( var key in methods ) {
                Model.prototype[key] = methods[key];
            }   
        }

        return Model;
    }
};

Usage

var User = ActiveRecord.create('users',
    /* fields and their default values */
{
    id: 0,
    username: '',
    password: '',
    post_count: 0,
    profile: ''
}, {
    /*instance methods */
    setPassword: function(password) {
        this.fields.password = password;
    }
});

/*You can define static methods like this */

User.findByUsername = function(username) {
    /*select from this.tableName where userName = username
               return new this(columnValues) blabla
             */
};

var jessica = User.create( {
    username: "Jessica",
    password: "rabbit"
});

jessica.save();

User.findByUsername('Jessica');
Esailija
  • 138,174
  • 23
  • 272
  • 326
1

You can extend the Object's prototype adding an "extends" method to Object.prototype. This way you will have an inherithance method that look like java.

(it's important to define the "extends" property as not enumerable otherwise it will break jQuery)

Object.defineProperty(Object.prototype, "extends", {
        "enumerable": false,
        "value" : function(constructor) {

                /*Inheriting enumerable statick method and paramether
                from the super class */
                Object.keys( constructor ).forEach( function(key) {
                        this[key]= constructor[key];
                }.bind(this));

                /*Classic Javascript inheritance*/
                this.prototype= Object.create( constructor.prototype, {
                            "constructor": {
                                    "value": this,
                                    "configurable": true
                            }
                });             

                this.__super__= constructor;

        }

});

After you can easily inherit one class by an other by doing so:

InheritedModel.extends(Model);

function InheritedModel(params){

       this.constructor.__super__.call(this, params);

       /* Or directly :
       Model.call(this,param);
       */

       /*Code specific to InheritedModel constructor */
}

/*To overload method from the super class:*/

InheritedModel.prototype.foo= function(params){

     var out= this.constructor.__super__.prototype.foo.call(this,params);

     /* Or
     var out= Model.prototype.foo.call(this,param);
     */

     /* code */

};

InheritedModel will inherit of all the instance method and all static method from model.

Example:

function Model() {
        this.inheritedClassName= "Model";
};

Model.inheritedClassName= "Model";

Model.getClassName = function() {
       return this.name;
};

Model.prototype.getClassName = function() {
        return this.constructor.name;
};

InheritedModel.extends(Model);

function InheritedModel() {
        Model.call(this);
}

console.log(InheritedModel.inheritedClassName);/* Model */
console.log(InheritedModel.getClassName());/* InheritedModel */

var inheritedModel= new InheritedModel();

console.log(inheritedModel.inheritedClassName);/* Model */
console.log(inheritedModel.getClassName());/* InheritedModel */

I think this is the best solution.

Joseph Garrone
  • 1,662
  • 19
  • 20
-1

what about this, you have private and public methods:

function Model() {
    var privateMethods = {
       private1: function() {},
       private2: function() {},
       private3: function() {},
    };

    var publicMethods = {
    method1: function() {},
    method2: function() {
            //call a private method...
            privateMethods.private1();
        }
    };

    return publicMethods;
}


// Static methods
Model.all = function() {};

Model.find = function() {};

Model.create = function() {
    return new Model();
};
silly
  • 7,789
  • 2
  • 24
  • 37