In code from 2016 using sequelize ORM, I see model types defined with this pattern:
module.exports = function(sequelize, DataTypes) {
const Tasks = sequelize.define("Tasks", { id: {
type: DataTypes.INTEGER,
[ ...etc.]
However in the current sequelize docs you see most prominently documented: Sequelize.INTEGER (or other type then integer).
At the same time in the current docs I find also DataTypes still documented/used: here.
On same page the Sequelize.INTEGER is used..., is that only for deferrables or something?
I tried to find whether this altered over time or something but could not find it.
When Sequelize.INTEGER is 'current solution' could I just alter above code into:
module.exports = function(sequelize, Sequelize) {
const Tasks = sequelize.define("Tasks", { id: {
type: Sequelize.INTEGER,
[ ...etc.]
Or would using Sequelize as argument somehow make this fail?
The second parameter in both of them is just the sequelize package itself You can use any of them which is on you what you want to use
const Sequelize = require('sequelize');
You'll notice in your index.js of models (if you set up as suggested) that you do something like the below, where you are passing in sequelize as the second argument.
const model = require(path.join(__dirname, file))(sequelize, Sequelize);
This exposes the data types. It doesn't matter what you call it. For example I am calling it abc in below code you can use any name
module.exports = (sequelize, abc) => {
const Driver = sequelize.define('Driver', {
firstName: {
type: abc.STRING(),
allowNull: false
},
last_name: {
type: abc.TEXT,
allowNull: true
},
email: {
type: abc.TEXT,
allowNull: false
},
password: {
type: abc.TEXT,
allowNull: true
}
Same with migrations.
Related
As a beginner in node js I cannot wrap my head around following problem.
import { createSchema, Type, typedModel } from "ts-mongoose";
const CompanySchema = createSchema(
{
companyName: Type.string({ required: true, unique: true })
},
{
timestamps: true
}
);
const Company = typedModel("Company", CompanySchema);
export { CompanySchema, Company };
This all works just fine until one point. When attempting to import this file.
import {CompanySchema, Company} from "./Company";
It executes typeModel method and stores the schema as expected. However, any other import of this file Company.ts reruns this method typeModel again. Which then fails because I can register schema with the name only once. How could I prevent of reruning this and still keep access to this object?
What would be general approach to this in order to keep access to both CompanySchema and Company object(as they will be later used in another schema as a reference)?
I don't know what the createSchema and typedModel are( if they are functions created by you or part of mongoose, the versions of mongoose ive worked with didnt have these functions )
...but I think you should not "createSchema" but define it instead.
e.g
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// define schema
const messageSchema = Schema({
sender: { type: Schema.Types.ObjectId, ref: 'User' },
recipient: { type: Schema.Types.ObjectId, ref: 'User' },
createdAt: { type: Date, default: Date.now },
deletedAt: { type: Date, default: undefined },
readAt: { type: Date, default: undefined},
message: { type: String, maxlength: 5000 }
});
// create a model based on that schema
const Message = mongoose.model('Message', messageSchema);
// Export the message model ... not the schema
module.exports.Message = Message;
Is it possible to define fields at the schema level that are based off of another field using mongoose schemas?
For example, say I have this very simple schema:
const mongoose = require('mongoose')
const { Schema } = mongoose
const UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
username_lower: {
type: String,
// calculated: this.username.toLowerCase()
},
email: {
type: String,
required: true,
unique: true
},
email_lower: { // for case-insensitive email indexing/lookup
type: String,
// calculated: this.email.toLowerCase()
},
password: {
type: String,
required: true
},
password_acceptable: {
type: Array,
// calculated: [
// this.password.toLowerCase(),
// this.password.toUpperCase()
// this.password.removeWhiteSpace(), // just an example
// ]
}
})
const User = mongoose.model('User', UserSchema)
module.exports = User
Is there something similar to the dummy "calculated" fields (that I've commented out) that would allow automatic field creation when the new document is saved? This would be very convenient and would reduce the clutter from having to manually define these fields on my back-end routes.
Thank you very much for your help!
you can do it by Pre middleware function, for more details
UserSchema.pre('save', function(){
this.username_lower = this.username.toLowerCase();
this.email_lower = this.email.toLowerCase();
// and so on ...
next();
});
I have a GraphQL/Apollo server using Sequelize/mysql. My GraphQL types (Employee, Contractor) each implement a Person interface. My db model contains an employee, contractor, and events table. I would like my Person Type to have a "has many" relationship with Events. While my Event Type "belongs to" a Person Type either Employee or Contractor.
I'm guessing it has something do do with the person_id field in the Event Type. I can get it to work without the interface on a single table "Employee" and changing person_id to employee_id. So im guessing it just doesnt know how to distinguish between Employee and Contractor to reference that table?
//typeDefs.js
const typeDefs = gql`
type Event {
id: ID!
person_id: ID!
location: String!
escort: String!
}
interface Person {
id: ID!
first_name: String!
last_name: String!
department_company: String!
events: [Event]
}
type Employee implements Person {
id: ID!
first_name: String!
last_name: String!
department_company: String!
events: [Event]
employee_sh_id: String
}
type Contractor implements Person {
id: ID!
first_name: String!
last_name: String!
department_company: String!
events: [Event]
escort_required: Boolean!
}
//Employee model
module.exports = (sequelize, DataTypes) => {
const Employee = sequelize.define('Employee', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
department_company: DataTypes.STRING,
emplyee_sh_id: DataTypes.STRING
}, {});
Employee.associate = function(models) {
Employee.hasMany(models.Event);
};
return Employee;
};
// Contractor model
module.exports = (sequelize, DataTypes) => {
const Contractor = sequelize.define('Contractor', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
department_company: DataTypes.STRING,
escort_required: DataTypes.BOOLEAN,
}, {});
Contractor.associate = function(models) {
Contractor.hasMany(models.Event);
};
return Contractor;
};
// Event model
module.exports = (sequelize, DataTypes) => {
const Event = sequelize.define(
"Event",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
person_id: DataTypes.INTEGER,
location: DataTypes.STRING,
escort: DataTypes.STRING
},
{}
);
Event.associate = function(models) {
Event.belongsTo(models.Employee),
Event.belongsTo(models.Contractor)
};
return Event;
};
// resolvers.js
const resolvers = {
Query: {
async employee(root, { id }, { models }) {
return models.Employee.findByPk(id);
},
async contractor(root, { id }, { models }) {
return models.Contractor.findByPk(id);
},
async employees(root, args, { models }) {
return models.Employee.findAll();
},
async contractors(root, args, { models }) {
return models.Contractor.findAll();
},
async event(root, { id }, { models }) {
return models.Event.findByPk(id);
},
async events(root, args, { models }) {
return models.Event.findAll();
}
},
Mutation: {
async addEmployee(
root,
{
first_name,
last_name,
department_company,
employee_sh_id
},
{ models }
) {
return models.Employee.create({
first_name,
last_name,
department_company,
employee_sh_id
});
},
async addContractor(
root,
{
first_name,
last_name,
department_company,
escort_required,
},
{ models }
) {
return models.Contractor.create({
first_name,
last_name,
department_company,
escort_required,
});
},
async addEvent(
root,
{ person_id, location, escort },
{ models }
) {
return models.Event.create({
person_id,
location,
escort
});
},
Person: {
__resolveType: person => {
if (person.employee) {
return "Employee";
}
return "Contractor";
}
},
Employee: {
events: (parent, args, context, info) => parent.getEvents(),
},
Contractor: {
events: (parent, args, context, info) => parent.getEvents(),
}
};
Do you even need an interface?
The primary purpose behind abstract types like interfaces and unions is that they allow a particular field to resolve to one of a set of types. If you have Contractor and Employee types and want a particular field to return either type, it makes sense to add an interface or union to handle that scenario:
type Event {
contractors: [Contractor!]!
employees: [Employee!]!
people: [Person!]! # could be either
}
If you don't need this functionality, you don't really need any abstract types. (Side note: you can use interfaces just to enforce congruence between types that share fields, but at that point you're just using them as a validation tool and their existence probably shouldn't impact how you design the underlying data layer).
There is no silver bullet
Dealing with inheritance in relational databases can be tricky and there's no one-size-fits-all answer. Things get even weirded when using Sequelize or another ORM because your solution has to work within the limits of that particular library as well. Here's a couple of different ways you could approach this problem, though it's by far not an exhaustive list:
If you only have a couple of fields that return a Person type, you can get away with having separate tables and separate models and just merging the results yourself. Something like:
people: async (event) => {
const [employees, contractors] = await Promise.all([
event.getEmployees(),
event.getContractors(),
])
const people = employees.concat(contractors)
// Sort here if needed
return people
}
This does mean you're now querying the DB twice, and potentially spending extra time doing sorting that the DB would have otherwise done for you. However, it means you can maintain separate tables and models for Contractors and Employees, which means querying for those entities is straightforward.
Lump both Contractors and Employees under a single table, using some kind of type field to distinguish between the two. You can then use scopes to help you model the relationships appropriately in Sequelize:
Event.hasMany(models.Person, { as: 'employees', scope: {type: 'EMPLOYEE'} ... })
Event.hasMany(models.Person, { as: 'contractors', scope: {type: 'CONTRACTOR'} ... })
Event.hasMany(models.Person, { as: 'people', /** no scope **/ ... })
This works, even if it doesn't "feel" right to have everything in one table. You have to remember to scope your associations and queries correctly.
If you're using Sequelize strictly as an ORM and not generating your database from your Sequelize models (i.e. not calling sync), it's also possible to model a view as a Sequelize model. Views are a bit of a pain to write and maintain, but this would allow you keep separate tables for Employees and Contractors while creating a virtual table from the other two that could be used to query all People.
Update
This morning I wanted to use another inbuilt sequelize function and suddenly the belongsTo() and hasMany() functions were available and working on some models -not all! These models have all exactly the same pattern and only differ in name.
For example hasOne is not available for topicClass
I didn't change anything else. Except I restarted intelliJ. I did restart it before I opened this question and always had the same issue. So I think this issue is IDE based! Still would appreciate a tip how to avoid this
I have an APP using sequelize with SQLite.
When I want to setup the associations in my db-controller.js it tells me that the association functions like belongsTo and hasmany from the docu are not available.
When I execute the setupAssociations function and check the db later there are the models but no associations set.
Here the db-controller.js (more info from my side after the models at the bottom!)
const Sequelize = require('sequelize')
const userModel = require('../model/user')
const subjectModel = require('../model/subject')
// eslint-disable-next-line no-unused-vars
const initUser = userModel.user.initUser()
// eslint-disable-next-line no-unused-vars
const initSubject = subjectModel.subject.initSubject()
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: './user.sqlite',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
logging: false
})
sequelize.sync()
function setupAssociations(){
// user + subject
userModel.user.userClass.hasMany(subjectModel.subject.subjectClass)
subjectModel.subject.subjectClass.belongsTo(userModel.user.userClass)
// subject + topic
}
function testAssociations(){
setupAssociations()
}
testAssociations()
and my two models
const Sequelize = require('sequelize')
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: '../controller/user.sqlite',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
logging: false
})
sequelize.sync()
/**
* User Model
*/
class User extends Sequelize.Model {
}
/**
* Init user model
*/
function initUser () {
//TODO optional realize with sequelize.transaction
User.init(
// attributes
{
firstName: {
type: Sequelize.STRING,
allowNull: false
},
lastName: {
type: Sequelize.STRING,
allowNull: false
},
email: {
type: Sequelize.STRING,
allowNull: false
},
title: {
type: Sequelize.STRING,
allowNull: false
},
password: {
type: Sequelize.TEXT,
allowNull: false
}
},
// options
{
sequelize,
modelName: 'user'
}
)
}
/**
* Makes functions available globally
*/
exports.user = {
initUser: initUser,
userClass: User
}
and Subject
const Sequelize = require('sequelize')
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: '../controller/user.sqlite',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
logging: false
})
sequelize.sync()
/**
// * Subject Model
*/
class Subject extends Sequelize.Model {
}
/**
* Initialises subject model
*/
function initSubject () {
Subject.init(
// attributes
{
subjectName: {
type: Sequelize.STRING,
allowNull: false
}
},
// options
{
sequelize,
modelName: 'subject'
}
)
}
/**
* Makes functions globally available
*/
exports.subject = {
initSubject: initSubject,
subjectClass: Subject
}
If I create another class like class TestClass extends Sequelize.Model in the user.js file and call hasMany with belongsTo within the initUser function and check the db after, then everything is as I want it to.
So what I do here wrong please?
I figured out that I had from another project a script running adding to the comment of the export function
#type {{initTopic: (function(): Promise<*>), topicClass: Topic}}
This caused the issue. Removing it made all sequelize functions appearing again.
I have seen the use of both classes for defining data types, including in the official documentation, both apparently serve the same purpose.
On a tutorial, I saw the application was using DataTypes for the Model and Sequelize for Migrations, you can exchange between them and they continue to work. Example codes:
Model using DataTypes:
module.exports = (sequelize, DataTypes) => {
const Driver = sequelize.define('Driver', {
firstName: {
type: DataTypes.STRING(50),
allowNull: false
},
Migration using Sequelize:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Drivers', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
The second parameter in both of them is just the sequelize package itself
const Sequelize = require('sequelize');
You'll notice in your index.js of models (if you set up as suggested) that you do something like the below, where you are passing in sequelize as the second argument.
const model = require(path.join(__dirname, file))(sequelize, Sequelize);
This exposes the data types. It doesn't matter what you call it. For example
module.exports = (sequelize, asd) => {
const Driver = sequelize.define('Driver', {
firstName: {
type: asd.STRING(50),
allowNull: false
},
Same with migrations.
As stated in the docs, DataTypes is:
A convenience class holding commonly used data types.