@ -6,8 +6,8 @@ const { generateLinkID } = require("../utils")
* Creates a new link document structure which can be put to the database . It is important to
* note that while this talks about linker / linked the link is bi - directional and for all intent
* and purposes it does not matter from which direction the link was initiated .
* @ param { string } model Id1 The ID of the first model ( the linker ) .
* @ param { string } model Id2 The ID of the second model ( the linked ) .
* @ param { string } table Id1 The ID of the first table ( the linker ) .
* @ param { string } table Id2 The ID of the second table ( the linked ) .
* @ param { string } fieldName1 The name of the field in the linker table .
* @ param { string } fieldName2 The name of the field in the linked table .
* @ param { string } recordId1 The ID of the record which is acting as the linker .
@ -15,65 +15,65 @@ const { generateLinkID } = require("../utils")
* @ constructor
* /
function LinkDocument (
model Id1,
table Id1,
fieldName1 ,
recordId1 ,
model Id2,
table Id2,
fieldName2 ,
recordId2
) {
// build the ID out of unique references to this link document
this . _ id = generateLinkID ( modelId1 , model Id2, recordId1 , recordId2 )
this . _ id = generateLinkID ( tableId1 , table Id2, recordId1 , recordId2 )
// required for referencing in view
this . type = "link"
this . doc1 = {
modelId : model Id1,
tableId : table Id1,
fieldName : fieldName1 ,
recordId : recordId1 ,
}
this . doc2 = {
modelId : model Id2,
tableId : table Id2,
fieldName : fieldName2 ,
recordId : recordId2 ,
}
}
class LinkController {
constructor ( { instanceId , model Id, record , model , oldModel } ) {
constructor ( { instanceId , table Id, record , table , oldTable } ) {
this . _ instanceId = instanceId
this . _ db = new CouchDB ( instanceId )
this . _ modelId = model Id
this . _ tableId = table Id
this . _ record = record
this . _ model = model
this . _ oldModel = oldModel
this . _ table = table
this . _ oldTable = oldTable
}
/ * *
* Retrieves the model , if it was not already found in the eventData .
* @ returns { Promise < object > } This will return a model based on the event data , either
* if it was in the event already , or it uses the specified model Id to get it .
* Retrieves the table , if it was not already found in the eventData .
* @ returns { Promise < object > } This will return a table based on the event data , either
* if it was in the event already , or it uses the specified table Id to get it .
* /
async model ( ) {
if ( this . _ model == null ) {
this . _ model =
this . _ model == null ? await this . _ db . get ( this . _ model Id) : this . _ model
async table ( ) {
if ( this . _ table == null ) {
this . _ table =
this . _ table == null ? await this . _ db . get ( this . _ table Id) : this . _ table
}
return this . _ model
return this . _ table
}
/ * *
* Checks if the model this was constructed with has any linking columns currently .
* If the model has not been retrieved this will retrieve it based on the eventData .
* @ params { object | null } model If a model that is not known to the link controller is to be tested .
* Checks if the table this was constructed with has any linking columns currently .
* If the table has not been retrieved this will retrieve it based on the eventData .
* @ params { object | null } table If a table that is not known to the link controller is to be tested .
* @ returns { Promise < boolean > } True if there are any linked fields , otherwise it will return
* false .
* /
async doesModelHaveLinkedFields ( model = null ) {
if ( model == null ) {
model = await this . model ( )
async doesTableHaveLinkedFields ( table = null ) {
if ( table == null ) {
table = await this . table ( )
}
for ( let fieldName of Object . keys ( model . schema ) ) {
const { type } = model . schema [ fieldName ]
for ( let fieldName of Object . keys ( table . schema ) ) {
const { type } = table . schema [ fieldName ]
if ( type === "link" ) {
return true
}
@ -87,7 +87,7 @@ class LinkController {
getRecordLinkDocs ( recordId ) {
return getLinkDocuments ( {
instanceId : this . _ instanceId ,
model Id: this . _ model Id,
table Id: this . _ table Id,
recordId ,
includeDocs : IncludeDocs . INCLUDE ,
} )
@ -96,15 +96,15 @@ class LinkController {
/ * *
* Utility function for main getLinkDocuments function - refer to it for functionality .
* /
getModel LinkDocs ( ) {
getTable LinkDocs ( ) {
return getLinkDocuments ( {
instanceId : this . _ instanceId ,
model Id: this . _ model Id,
table Id: this . _ table Id,
includeDocs : IncludeDocs . INCLUDE ,
} )
}
// all operations here will assume that the model
// all operations here will assume that the table
// this operation is related to has linked records
/ * *
* When a record is saved this will carry out the necessary operations to make sure
@ -113,15 +113,15 @@ class LinkController {
* have also been created .
* /
async recordSaved ( ) {
const model = await this . model ( )
const table = await this . table ( )
const record = this . _ record
const operations = [ ]
// get link docs to compare against
const linkDocs = await this . getRecordLinkDocs ( record . _ id )
for ( let fieldName of Object . keys ( model . schema ) ) {
for ( let fieldName of Object . keys ( table . schema ) ) {
// get the links this record wants to make
const recordField = record [ fieldName ]
const field = model . schema [ fieldName ]
const field = table . schema [ fieldName ]
if ( field . type === "link" && recordField != null ) {
// check which links actual pertain to the update in this record
const thisFieldLinkDocs = linkDocs . filter (
@ -139,10 +139,10 @@ class LinkController {
if ( linkId && linkId !== "" && linkDocIds . indexOf ( linkId ) === - 1 ) {
operations . push (
new LinkDocument (
model . _ id ,
table . _ id ,
fieldName ,
record . _ id ,
field . model Id,
field . table Id,
field . fieldName ,
linkId
)
@ -193,17 +193,17 @@ class LinkController {
}
/ * *
* Remove a field from a model as well as any linked records that pertained to it .
* @ param { string } fieldName The field to be removed from the model .
* @ returns { Promise < void > } The model has now been updated .
* Remove a field from a table as well as any linked records that pertained to it .
* @ param { string } fieldName The field to be removed from the table .
* @ returns { Promise < void > } The table has now been updated .
* /
async removeFieldFromModel ( fieldName ) {
let oldModel = this . _ oldModel
let field = oldModel . schema [ fieldName ]
const linkDocs = await this . getModel LinkDocs ( )
async removeFieldFromTable ( fieldName ) {
let oldTable = this . _ oldTable
let field = oldTable . schema [ fieldName ]
const linkDocs = await this . getTable LinkDocs ( )
let toDelete = linkDocs . filter ( linkDoc => {
let correctFieldName =
linkDoc . doc1 . modelId === oldModel . _ id
linkDoc . doc1 . tableId === oldTable . _ id
? linkDoc . doc1 . fieldName
: linkDoc . doc2 . fieldName
return correctFieldName === fieldName
@ -216,83 +216,83 @@ class LinkController {
}
} )
)
// remove schema from other model
let linkedModel = await this . _ db . get ( field . model Id)
delete linkedModel . schema [ field . fieldName ]
this . _ db . put ( linkedModel )
// remove schema from other table
let linkedTable = await this . _ db . get ( field . table Id)
delete linkedTable . schema [ field . fieldName ]
this . _ db . put ( linkedTable )
}
/ * *
* When a model is saved this will carry out the necessary operations to make sure
* any linked model s are notified and updated correctly .
* When a table is saved this will carry out the necessary operations to make sure
* any linked table s are notified and updated correctly .
* @ returns { Promise < object > } The operation has been completed and the link documents should now
* be accurate . Also returns the model that was operated on .
* be accurate . Also returns the table that was operated on .
* /
async model Saved( ) {
const model = await this . model ( )
const schema = model . schema
async table Saved( ) {
const table = await this . table ( )
const schema = table . schema
for ( let fieldName of Object . keys ( schema ) ) {
const field = schema [ fieldName ]
if ( field . type === "link" ) {
// create the link field in the other model
const linkedModel = await this . _ db . get ( field . model Id)
linkedModel . schema [ field . fieldName ] = {
// create the link field in the other table
const linkedTable = await this . _ db . get ( field . table Id)
linkedTable . schema [ field . fieldName ] = {
name : field . fieldName ,
type : "link" ,
// these are the props of the table that initiated the link
modelId : model . _ id ,
tableId : table . _ id ,
fieldName : fieldName ,
}
await this . _ db . put ( linkedModel )
await this . _ db . put ( linkedTable )
}
}
return model
return table
}
/ * *
* Update a model , this means if a field is removed need to handle removing from other table and removing
* Update a table , this means if a field is removed need to handle removing from other table and removing
* any link docs that pertained to it .
* @ returns { Promise < Object > } The model which has been saved , same response as with the model Saved function .
* @ returns { Promise < Object > } The table which has been saved , same response as with the table Saved function .
* /
async model Updated( ) {
const oldModel = this . _ oldModel
async table Updated( ) {
const oldTable = this . _ oldTable
// first start by checking if any link columns have been deleted
const newModel = await this . model ( )
for ( let fieldName of Object . keys ( oldModel . schema ) ) {
const field = oldModel . schema [ fieldName ]
// this field has been removed from the model schema
if ( field . type === "link" && newModel . schema [ fieldName ] == null ) {
await this . removeFieldFromModel ( fieldName )
const newTable = await this . table ( )
for ( let fieldName of Object . keys ( oldTable . schema ) ) {
const field = oldTable . schema [ fieldName ]
// this field has been removed from the table schema
if ( field . type === "link" && newTable . schema [ fieldName ] == null ) {
await this . removeFieldFromTable ( fieldName )
}
}
// now handle as if its a new save
return this . model Saved( )
return this . table Saved( )
}
/ * *
* When a model is deleted this will carry out the necessary operations to make sure
* any linked model s have the joining column correctly removed as well as removing any
* When a table is deleted this will carry out the necessary operations to make sure
* any linked table s have the joining column correctly removed as well as removing any
* now stale linking documents .
* @ returns { Promise < object > } The operation has been completed and the link documents should now
* be accurate . Also returns the model that was operated on .
* be accurate . Also returns the table that was operated on .
* /
async model Deleted( ) {
const model = await this . model ( )
const schema = model . schema
async table Deleted( ) {
const table = await this . table ( )
const schema = table . schema
for ( let fieldName of Object . keys ( schema ) ) {
const field = schema [ fieldName ]
if ( field . type === "link" ) {
const linkedModel = await this . _ db . get ( field . model Id)
delete linkedModel . schema [ model . name ]
await this . _ db . put ( linkedModel )
const linkedTable = await this . _ db . get ( field . table Id)
delete linkedTable . schema [ table . name ]
await this . _ db . put ( linkedTable )
}
}
// need to get the full link docs to delete them
const linkDocs = await this . getModel LinkDocs ( )
const linkDocs = await this . getTable LinkDocs ( )
if ( linkDocs . length === 0 ) {
return null
}
// get link docs for this model and configure for deletion
// get link docs for this table and configure for deletion
const toDelete = linkDocs . map ( doc => {
return {
... doc ,
@ -300,7 +300,7 @@ class LinkController {
}
} )
await this . _ db . bulkDocs ( toDelete )
return model
return table
}
}