Skip to content

9.2 · Model registration + Models global

ทุก Sequelize model ใน vtrc-api ถูกโหลดอัตโนมัติตอน boot — ไม่ต้อง import เอง กลไกนี้อยู่ที่ models/index.js (63 บรรทัด) และเป็นจุดที่ dev ใหม่มักจะสับสนเพราะ "ทำไมไม่เห็น import User ใน resolver"


วิธี registration แบบ "auto-discovery"

6:21:vtrc-api/api/src/models/index.js
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 ทั้งหมด

27:35:vtrc-api/api/src/models/index.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)

37:45:vtrc-api/api/src/models/index.js
//[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()

47:56:vtrc-api/api/src/models/index.js
/** --- 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

58:63:vtrc-api/api/src/models/index.js
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:

javascript
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 bridge

Naming convention

สิ่งที่เห็นใน codeความหมาย
models/core/withdraw/hospital.jspath บน disk
db.define('WDHospital', {...})table name บน MariaDB (WDHospitals)
Models.WDHospitalkey ที่ใช้ใน code

ชื่อ model = ชื่อตัวแปร = db.define อาร์กิวเมนต์แรก แต่ชื่อตารางจริงใน DB มักจะเป็น plural (Sequelize default) — WDHospitalWDHospitals

บาง model override tableName โดยตรง:

237:243:vtrc-api/api/src/models/core/withdraw/hospital.js
  }, {
    // options
    tableName: 'WDHospitals',
    timestamps: true,
    charset: 'utf8',
    collate: 'utf8_general_ci',
    indexes: [{ fields: ['status'] }]
  })

สิ่งที่ควรรู้ตอนเพิ่ม model ใหม่

1. วางไฟล์ใน folder ที่ถูกต้อง

  • core/ ถ้าใช้ db connection (vtrc schema)
  • centralize/ ถ้าใช้ db2 connection (vtrc-centralize schema)

ถ้าวางผิดที่ → model ไปผูกกับ connection ผิด → query fail

2. Export default function

javascript
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 ที่ส่งเข้ามาแทน

javascript
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)68.5 ERD Identity
Leave & Approvals58.6 ERD Leave
Welfare Withdrawal88.7 ERD Withdrawal
Study Leave148.8 ERD Study
Provident Fund68.8 ERD Fund
Notification88.8 ERD Notification
Master Data11(รวมใน 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