yassine
yassine

Reputation: 21

How to find out programmatically if a sequelize model attribute's is a foreign key and to what model does it refer to

I am trying to make a dynamic update function for my application that does two things,

-checks if the new values are different from the old ones to avoid updating and thus changing the updated_at timestamp for no reason if no value is actually being changed

-checks if one of the attributes the user wants to change is a foreign key, and then checks if the said user modification refers to an actual row from the referred to model or not in order to avoid getting the error

so I need a way to extract information about attributes from the model the dynamically, and more precisely I need to be able to check if an attribute is a foreign key, and if it is I need to find the name of the model it is referring to

Upvotes: 1

Views: 543

Answers (2)

yassine
yassine

Reputation: 21

If anyone needs this, here is how i managed to do it :

///////////////////////////////////////////////////////////////////////
// Unique keys checks

const hasUniqueConflicts = async (model, parameters)=>{

  const uniqueFields = getUniqueFields(model, Object.keys(parameters))
  const returns = new Set()
  const uniqueParameters = uniqueFields.map(field=>{return {[field]:parameters[field]}})
  // Check if any of the values of the unique fields already exists
  const results = await model.findAll({where:{[Op.or]:uniqueParameters}})

  if(results.length){
    for(result of results){
      for (field of uniqueFields){
        if(parameters[field] === result[field]) returns.add(field)
      }
    }
    return Array.from(returns)
  }

  return false
}


///////////////////////////////////////////////////////////////////////
// Foreign keys checks

const hasForeignKeyProblems = async (model, parameters)=>{
  const foreignKeys = getForeignKeys(model, Object.keys(parameters))
  const returns = new Set()

  for(field of foreignKeys){
    const valid = referencedRowExists(model, field, parameters[field])
    if (!valid) returns.add(field)
  }

  if (returns.size) return Array.from(returns)
  return false
}

//////////////////////////////////////////////////////////////
const referencedRowExists = async (model, foreignKey, foreignKeyValue)=>{
  if(foreignKeyValue === null && isNullable(model, foreignKey)) return true

  const models = getModels(model)
  const tableName = getReferencedTableName(model, foreignKey)
  for (table in models){
      if (models[table].tableName == tableName){
          referenceExists = await rowExists(models[table], foreignKeyValue)
          if (referenceExists) return true
          break
      }
  }
  return false
}

const isNullable = (model, fieldName) => model.rawAttributes[fieldName].allowNull

const IsUnique = (model, fieldName) => model.rawAttributes[fieldName].unique

const IsForeignKey = (model, fieldName) => model.rawAttributes[fieldName].references && true

const getModels = (model) => model.sequelize.models

const getUniqueFields = (model, fieldNames) => fieldNames.filter(field=>IsUnique(model, field))

const getForeignKeys = (model, fieldNames) => fieldNames.filter(field=>IsForeignKey(model, field))

const getReferencedTableName = (model, foreignKey) => model.rawAttributes[foreignKey].references.model

const rowExists = async (model, parameter) => Object.keys(parameter).length ? !!(await model.count({where: parameter})) : false

Upvotes: 1

Greg Belyea
Greg Belyea

Reputation: 878

Check this out... looks like you can just use a hook for the first part on all your models... https://github.com/sequelize/sequelize/issues/8762. And i agree with Ken on the part above, your database will already prevent you from doing that so trap that error and customize what you wanna send back, instead of adding a bunch of preventative code.. Obviously you have some way to identify what model is the intended target of your input so you shouldn't need to worry about accessing attribute data.

Upvotes: 0

Related Questions