9.2 · Model registration + Models global
ทุก Sequelize model ใน vtrc-api ถูกโหลดอัตโนมัติตอน boot — ไม่ต้อง import เอง กลไกนี้อยู่ที่ models/index.js (63 บรรทัด) และเป็นจุดที่ dev ใหม่มักจะสับสนเพราะ "ทำไมไม่เห็น import User ใน resolver"
วิธี registration แบบ "auto-discovery"
var walk = function recursiveReadDir(dir) {
var results = []
var list = fs.readdirSync(dir)
list.forEach(function (file) {
file = path.join(dir, '/', file)
var stat = fs.statSync(file)
if (stat && stat.isDirectory()) {
/* Recurse into a subdirectory */
results = results.concat(walk(file))
} else {
/* Is a file */
results.push(file.replace(path.join(__dirname, '/'), ''))
}
})
return results
}walk() คือ recursive directory reader — เริ่มจาก models/core/ และ models/centralize/ แล้วเก็บไฟล์ .js ทั้งหมด
// [CORE] Recursive import Model from files
walk(path.join(__dirname, "core"))
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js')
})
.forEach(file => {
const model = require(path.join(__dirname, file)).default(db, Sequelize.DataTypes);
Models[model.name] = model
})สองรอบ — รอบ core (ใช้ connection db) และรอบ centralize (ใช้ db2)
//[CETRANLIZE] database center
walk(path.join(__dirname, "centralize"))
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js')
})
.forEach(file => {
const model = require(path.join(__dirname, file)).default(db2, Sequelize.DataTypes);
Models[model.name] = model
})หมายเหตุ — CETRANLIZE typo ใน comment ลามไปทั้ง codebase
Associate cycle
หลังจาก register model ทั้งหมด แล้ว loop ผ่านเพื่อเรียก associate() และ addIndex()
/** --- MANAGE ASSOCIATE --- **/
Object.keys(Models).forEach(modelName => {
if (Models[modelName].associate) {
Models[modelName].associate(Models)
}
if (Models[modelName].addIndex) {
Models[modelName].addIndex(Models)
}
})แต่ละ model export ฟังก์ชัน associate(models) (optional) ที่ประกาศ relation กับ model อื่น — ต้องรอทุก model register ก่อน แล้วถึงเรียก associate ทีหลัง ไม่งั้น reference ไม่ครบ
Models global
Models.db = db
Models.db2 = db2
Models.Op = Sequelize.Op
Models.Sequelize = Sequelize
export { Models }Models เป็น object ที่รวม:
- model ทั้งหมด (
Models.User,Models.WDHospital,Models.LeaveDocument, ...) - connection ทั้งสอง (
Models.db,Models.db2) — ใช้สำหรับ raw query - Sequelize operator (
Models.Op) — ใช้ใน where clause ({ [Op.or]: [...] }) - Sequelize class (
Models.Sequelize) — ใช้สำหรับ type reference
วิธี import ใน resolver:
import { Models } from '../models'
const user = await Models.User.findOne({ where: { userId } })
const result = await Models.db.query('SELECT ...', { type: QueryTypes.SELECT })โครงสร้าง folder ของ models
models/
├── index.js ← auto-loader
├── core/ ← vtrc schema (db connection)
│ ├── withdraw/ ← welfare withdrawal domain
│ │ ├── hospital.js ← WDHospital
│ │ ├── slip.js ← WDSlip
│ │ ├── document.js ← WDHospitalDocument
│ │ ├── history.js ← WDHistory
│ │ ├── export.js ← WDExport
│ │ ├── delegate.js ← Delegate
│ │ ├── approver.js ← Approver (shared)
│ │ └── ...
│ ├── leave/ ← leave domain
│ ├── fund/ ← provident fund domain
│ ├── study/ ← study leave domain
│ ├── session/ ← auth sessions
│ ├── notification/ ← notification queue
│ ├── post/ ← news/announcement
│ ├── document/ ← document repository
│ ├── masterData/ ← master config / hospital / sickness
│ ├── organization/ ← org structure
│ ├── role/ ← RBAC
│ ├── file/ ← file metadata
│ ├── bankAccount/ ← bank accounts
│ ├── contactUs/ ← contact directory
│ ├── course/ ← training courses
│ ├── career/ ← career section
│ ├── consent/ ← consent records
│ ├── conicle/ ← Conicle integration
│ ├── taxDraft/ ← tax document draft
│ ├── sendEmail/ ← email queue
│ ├── version/ ← version tracking
│ ├── log/ ← activity log
│ └── bank/ ← bank reference
└── centralize/ ← vtrc-centralize schema (db2 connection)
└── user/
├── user.js ← User bridge
└── userDivision.js ← UserDivision bridgeNaming convention
| สิ่งที่เห็นใน code | ความหมาย |
|---|---|
models/core/withdraw/hospital.js | path บน disk |
db.define('WDHospital', {...}) | table name บน MariaDB (WDHospitals) |
Models.WDHospital | key ที่ใช้ใน code |
ชื่อ model = ชื่อตัวแปร = db.define อาร์กิวเมนต์แรก แต่ชื่อตารางจริงใน DB มักจะเป็น plural (Sequelize default) — WDHospital → WDHospitals
บาง model override tableName โดยตรง:
}, {
// options
tableName: 'WDHospitals',
timestamps: true,
charset: 'utf8',
collate: 'utf8_general_ci',
indexes: [{ fields: ['status'] }]
})สิ่งที่ควรรู้ตอนเพิ่ม model ใหม่
1. วางไฟล์ใน folder ที่ถูกต้อง
core/ถ้าใช้dbconnection (vtrc schema)centralize/ถ้าใช้db2connection (vtrc-centralize schema)
ถ้าวางผิดที่ → model ไปผูกกับ connection ผิด → query fail
2. Export default function
export default (db, Sequelize) => {
const MyModel = db.define('MyModel', { ... })
MyModel.associate = (models) => { ... }
return MyModel
}ถ้า export เป็น object โดยตรง จะไม่ถูก register (auto-loader เรียก .default(db, Sequelize))
3. ชื่อ model ต้อง unique ทั้งระบบ
Models เป็น flat object — ถ้าสอง model ชื่อซ้ำ (เช่น core/file/file.js กับ centralize/file/file.js) ตัวหลังจะ override ตัวแรก
4. associate ต้องเรียกผ่าน Models parameter
อย่า import cross-model โดยตรง แบบ import User from '../../session/user' — ใช้ parameter models ที่ส่งเข้ามาแทน
MyModel.associate = (models) => {
models.MyModel.belongsTo(models.User, { foreignKey: 'userId' })
}5. คำนึงถึง load order
associate() รันหลัง register ครบทุก model — แต่ raw query ที่รันตอน define() จะ fail ถ้าตารางยังไม่ sync
รายการ model ทั้งหมด (95 ไฟล์)
Volume นี้จะไม่ลงรายละเอียดทุก model แต่จะ focus ที่ domain หลัก:
| Domain | จำนวน model | บทที่อธิบาย |
|---|---|---|
| Identity & Access (User, Session, Role) | 6 | 8.5 ERD Identity |
| Leave & Approvals | 5 | 8.6 ERD Leave |
| Welfare Withdrawal | 8 | 8.7 ERD Withdrawal |
| Study Leave | 14 | 8.8 ERD Study |
| Provident Fund | 6 | 8.8 ERD Fund |
| Notification | 8 | 8.8 ERD Notification |
| Master Data | 11 | (รวมใน domain ที่ใช้) |
| Other (post, doc, course, file, ...) | ~37 | (ไม่ลงรายละเอียดใน Vol 9) |
เคล็ดลับตอนทำงานจริง
ถ้า model ใหม่ไม่ขึ้นใน Models
- เช็กว่า export default ถูกไหม
- เช็กว่าไฟล์อยู่ใน
core/หรือcentralize/ไหม - เช็กว่าไฟล์นามสกุล
.js(ไม่ใช่.ts— vtrc-api เป็น plain JS)
ถ้า relation ไม่ work
- เช็กว่า
associate()ประกาศทั้งสองฝั่งไหม (ฝั่ง hasMany + ฝั่ง belongsTo) - เช็ก alias —
hasMany(models.WDSlip, { as: 'slip' })ต้องใช้ alias เดียวกับที่ query ตอนinclude: [{ as: 'slip' }]
อย่า query ตอน model กำลัง register
boot sequence คือ register → associate → syncModelToTable → พร้อมใช้งาน ถ้ามี code ที่รันตอน define() (เช่น lookup default value) — จะ fail เพราะตารางยังไม่ sync
ไป 8.3 Model patterns →