-
Notifications
You must be signed in to change notification settings - Fork 0
Suggestion for Inheritance API
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 AnotherModel -
ADao.extends(AnotherDao)
to test if ADao is child of AnotherDao -
AModel.superclasses(AnotherModel)
to test if AModel superclasses AnotherModel -
ADao.superClasses(AnotherDao)
to test if ADao is parent of AnotherDao -
virtual
would just be a parameter indicating that this model cannot be instantiated -
AModel.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/Dao -
model
andincludeDescendants
would be options for find and findAllwhere
clause. 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 || hash
would 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.
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'})
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
})
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
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
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
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
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
}
}
}
})
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
Getting Started
- Installation
- Basic Setup
- With Express.JS
- Sequelize CLI
- Relationships
- 1 : 1
- 1 : n
- n : m
- Polymorph
- Hooks
- Pooling
- Debugging
- Migrations
How To
Resources
Upgrade Guides
- 3.0 to 4.0
- 2.0 to 3.0
- 1.7 to 2.0
Help