So here's the deal. I'd like to implement the inheritance functionality to sequelize models.
Those would be the main API add ons :
AModel.extends(AnotherModel)would serve as indicating which type is the model's "supermodel"AModel.extends(AnotherModel)also to test if AModel is child of AnotherModelADao.extends(AnotherDao)to test if ADao is child of AnotherDaoAModel.superclasses(AnotherModel)to test if AModel superclasses AnotherModelADao.superClasses(AnotherDao)to test if ADao is parent of AnotherDaovirtualwould just be a parameter indicating that this model cannot be instantiatedAModel.super()andADao.super()would be called from a class method, instance method and validator methods. It would access the superclass overridden method(?)AModel.super(AnotherModel)andADao.super(AnotherDao)would call the overridden method in the parent Model/DaomodelandincludeDescendantswould be options for find and findAllwhereclause. Model would be the sought Model, and includeDescendants would allow all Model descendent Models. e.g.AnotherModel.findAll(where : {model : Model, includeDescendants : true})serial || hierarchical || hashwould be options in findAll. Would represent output type. Like :AnotherModel.findAll({model : Model}, 'serial || hierarchical || hash')
var Feline = sequelize.define('Feline', {}, {virtual : true})
var Cat= sequelize.define('Cat', {})
var Lion = sequelize.define('Lion', {})
Cat.extends(Feline)
Lion.extends(Cat)
var FelineFood = sequelize.define('AnimalFood', {for : Feline},{virtual : true})
var CatFood = sequelize.define('CatFood', {name : sequelize.STRING, for : Cat})
var LionFood = sequelize.define('LionFood')
var GarfieldFood = sequelize.define('CatFood', {
for : { //Possible to redefine a field's validation properties without creating a new column
validate : function(instance){
super()
if (instance.name !== 'Garfield') { //Can access instance attributes
raise ("This ain't for Garfield")
} } } }
Let's take a use case :
var Animal = sequelize.define(Animal, { paws : sequelize.INTEGER, name : sequelize.STRING })
var Reptile = sequelize.define(Reptile, { eggs : sequelize.INTEGER })
var Mammal = sequelize.define(Mammal, { mammae : sequelize.INTEGER }, {virtual : true})
Reptile.extends(Animal)
Mammal.extends(Animal)
var gekko = Reptile.create({ name : 'gekko', paws : 4, eggs : 2}).complete(function(err, reptile){ })
// would create an animal and a reptile entry in the database
Reptile.findAll({where : {paws : 4}})
// would return all reptiles which are animals with 4 paws
Reptile.extends(Mammal) // would return false
var animal = Animal.create({ paws : 3 })
// would return an error because it is not allowed to instantiate a virtual class
Basically, this feature would mimic the class-based inheritance hierarchy. I think it would be useful to create complex data structures.
Bigger Example
Let's take another more complex example
// Parties models
// Parties
var Party = sequelize.define('Party', {}, {virtual : true})
// A person..
var Person = sequelize.define('Person', {firstname : sequelize.STRING, lastname : sequelize.STRING})
// An organization..
var Organization = sequelize.define('Organization, {name : sequelize.STRING})
// A position (job) available or not
var Position = sequelize.define('Position',{description : sequelize.STRING})
// Person, Position and Organization are parties
Person.extends(Party)
Organization.extends(Party)
Position.extends(Party)
// Organizations can be hierarchical
Party.hasOne(Party, {as : 'Parent'})
In this example, the Relation model is used to link two Parties, regardless of their type.
A Responsibility relation is a subtype of Relation. Represents a Responsibility towards another Party type
An Authorization relation is a subtype of Relation. Represents an Authorization towards another Party type
A Family relation is a subtype of Relation. Represents a familial link between two parties. It is restricted between two instances of Party of type Person.
// Relationship Models
// Relationships
var Relation = sequelize.define('Relation' { begin : sequelize.DATE, end : sequelize.DATE }, {virtual : true})
// Responsibility relationship
var Responsibility = sequelize.define('Responsibility', {})
// Authorization relationship
var Authorization = sequelize.define('Authorization', {accessLevel : sequelize.INTEGER})
// Family relationship
var Family = sequelize.define('Family', {accessLevel : sequelize.INTEGER})
// Responsibility and Authorization are relationships
Responsibility.extends(Relation)
Authorization.extends(Relation)
Family.extends(Relation)
// A relation identifies a source party with a target party
Relation.hasOne(Party, {as : 'Source'})
Relation.hasOne(Party, {as : 'Target})
The Locator type is used to represent an address, a telephone number or something used to communicate with a specified party, be it an Organization, a Person or a Position. It has a property Located which represents the Party being located by this Locator
The Locator model is the base type for locators
The TelephoneLocator model extends Locator. Used to represent a phone number
The ExtensionLocator model extends Locator. Used to represent an extension number. Is linked to another TelephoneLocator. If the linked TelephoneLocator number changes, the ExtensionLocator still has the reference to that number.
//Locator Models
//Locators is used to represent an address, phone number, etc. identifying a person's mean of communication
var Locator = sequelize.define('Locator', { })
//The party being identifier (organization, person, position)
Locator.hasOne(Party, { as : 'Located'})
//Plain telephone number
var TelephoneLocator = sequelize.define('TelephoneLocator', { number : sequelize.STRING })
TelephoneLocator.extends(Locator)
//The extension of a telephone number. E.g. office / post number
var ExtensionLocator = sequelize.define('ExtensionLocator', { extension : sequelize.INTEGER })
ExtensionLocator.extends(Locator)
//An ExtensionLocator identifies a Party, but is dependent on a base telephone number.
// Does not subclass it but instead is linked to it
ExtensionLocator.hasOne(TelephoneLocator, {as : 'LocatedPhone'})
Practical Example
Let's dive into a practical example.
John is a man.
var john;
// John is a nice man
Person.create({firstName : 'John', lastName : 'Doe'}).complete(function(err, person){
john = person
})
Acme is a company in his town
var acme;
Organization.create({name : 'Acme'}).complete(function(err, organization){
acme = organization
})
This is their phone number
var phone
TelephoneLocator.create({located : acme, number : '872-394-1834'}).complete(function(err, telLocator)){
phone = telLocator;
}
They hire a security guard
var securityGuard
Position.create({parent : acme, description : 'Security Guard'}).complete(function(err, position){
securityGuard = position
})
He has special authorizations
var auth
Authorization.create({source : acme, target : securityGuard, accessLevel : 101}).complete(function(err, anAuth){
auth = anAuth;
})
This is his extension number
var extension
ExtensionLocator.create({located : securityGuard, locatedPhone : phone, extension : '911'}).complete(function(err, anExtension){
extension = anExtension
})
John works as a security guard
var job
Responsibility.create({source : john, target : securityGuard, begin : now(), end : null})
.complete(function(err, responsibility){
job = responsibility
})
Also has a cellphone for lonely nights
var cellNumber
TelephoneLocator.create({located : john, number : '222-143-1234'}).complete(function(err, cellNumber)){
cellNumber = telLocator;
}
(Let's say this method is in the instance methods)
Person.quits = function(instance){
Responsibility.find(where : {source : instance}).complete(err, responsibility){
responsibility.setEndDate(now()).complete(function(err){
return 'Free time!';
})
})
}
John doesn't like his job
john.quits();
Finds another job
var anotherJob
Responsibility.create({source: john, target : anotherJob}).complete(function(err, resp){
anotherJob = resp
})
This is helen
var helen
Person.create({firstName : 'Hellen', lastName: 'Altfest'}).complete(function(err, person){
helen = person;
})
His sister
var family // (family relationship)
Family.create({source : john, target: helen}).complete(function(err, sister){
sister = relation
})
FindAll return types
Let's see john's relations
Relation.findAll(where : {source : john}).complete(function(err, relations){
console.log(relations)
// Outputs a hash with type as key
// Responsibilities :
// [0] job
// [1] anotherJob
// Family
// [0] helen
})
// And Helen's relations
// Outputs a hash with type as key
// Family
// [0] john
Let's define a BigResponsibility
BigResponsibility = sequelize.define('BigResponsibility', {})
BigResponsibility.extends(Responsibility)
var bigResponsibility
BigResponsibility.create({source : john, target : acme}).complete(function(err, bigResp){
bigResponsibility = bigResp;
})
// Johns relations are now :
// Outputs a hash with type as key
// Responsibilities :
// [0] job
// [1] anotherJob
// BigResponsibilities
// [0] bigResponsibility
// Family
// [0] helen
Hierarchical Form
Let's get this data in hierarchical form
Person.findAll({where : {name : 'john'}, {hierarchical : true}})
// Option to have hierarchical structure
// Johns relations
// Outputs a hash with type as key
// Relations :
// Children :
// Responsibilities :
// Children :
// BigResponsibilities :
// [0] : bigResponsibility
// Data :
// [0] job
// [1] anotherJob
// Family
// Data :
// [0] helen
Serial Form
And in serial form
Person.findAll({where : {name : 'john'}, {serial : true}})
// Option to have hierarchical structure
// Johns relations
// [0] job
// [1] helen
// [2] anotherJob
// [3] bigResponsibility
model and includeDescendants
The model and includeDescendants option
Person.find({where : {name : 'john', type : {model : responsibility, includeDescendants : true }}} //Default
// Johns relations
// Outputs a hash with type as key
// Responsibilities :
// [0] job
// [1] anotherJob
// BigResponsibilities
// [0] bigResponsibility
If it is set to false
Person.find({where : {name : 'john', type : {model : Responsibility, includeDescendants : false }}}
// Johns relations
// Outputs a hash with type as key
// Responsibilities :
// [0] job
// [1] anotherJob
Validation
var Responsibility = sequelize.define('Responsibility', {
// Sequelize does not add another 'source' field as 'source' exists already a parent class
source : {
validate : {
type : function(instance){
super() //Calls the parent type validator method
if (instance.Model !== 'Person') {raise...}} // It just validates the type
}
}
// same here, do not add another 'target' field as it already exists.
target : {
type : sequelize.INTEGER // error when trying to redefine overridden field type
validate : {
type : function(instance){
super()
if ((instance.Model !== Organization) || (instance.Model !== Position))
raise...
if (!instance.Model.inheritsFrom(Party))
raise
}
}
}
})
Redefinition of Association Rules
Would also be nice to be able to redefine association rules, like : (But could also be done with validation methods)
Animal = sequelize.define('Animal', {})
Cat = sequelize.define('Cat', {})
Dog = sequelize.define('Dog', {})
Cat.extends(Animal)
Dog.extends(Animal)
Person = sequelize.define('Person',{})
GrandMa = sequelize.define('GrandMa', {})
GrandMa.extends(Person)
Person.hasMany(Animals, {as : 'pets'}) //Person type can have any type of animal as pets
Grandma.hasMany(Cats, {as : 'pets'}) //GrandMa type can only haz cats as pets
GrandMa.getPets() //Would return all GrandMa's pets
Dog.create().complete(function(err, dog){
GrandMa.addPet(dog).complete(function(err){ //Throws an error. Grandma can only have cats!
})
})
And more to come
Resources
Upgrade Guides
Help