[Seqeulize 공식문서 번역하기] Model Basic

피누·2020년 3월 9일
3

원서와 친해지기

목록 보기
2/3
post-thumbnail

해당 자료는 seqeulize 5의 공식문서를 번역한 자료입니다.

Model Basics

이번 튜토리에서 Seuqelize의 모델이 무엇이고 어떻게 사용하는지 배워보자

Concept

Models은 Sequelize의 본질이다. Model은 데이터베이스의 테이블를 추상화하여 나타낸 것이다. Sequelize에서는 Model을 확장한 클래스로 나타난다.

Model은 Sequlize에게 테이블의 이름이나 컬럼, 데이터 타입등과 같은 정보를 전달한다.

Model은 이름을 가지고 있는데, 이 이름이 반드시 데이터베이스 테이블이름과 같을 필요는 없다. 일반적으로 테이블 이름이 복수형(Users와 같은)으로 나타나는 반면 Model은 단수형 이름(User와 같은)을 가진다. 물론 이는 configurable하다.

Model Definition

Model은 동등한 두 가지 방법으로 정의 될 수 있다.

  • Calling sequelize.define(modelName, attributes, options)
  • Extending Model and calling init(attributes, options)
    Model을 정의한 후에 Model 이름을 통해 seqeulize.models내에서 사용 가능하다.

firstNamelastName을 가지는 user model을 만든다고 가정해보자. Model의 이름은 User로 이는 데이터베이스의 Users을 나타낸다.

이 모델을 정의하는 방법은 다음과 같다. 모델을 정의한 후에 우리는 seqeulize.models.User로 access 할 수 있다.

Using sequelize.define:

const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');

const User = sequelize.define('User', {
  // Model attributes are defined here
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName: {
    type: DataTypes.STRING
    // allowNull defaults to true
  }
}, {
  // Other model options go here
});

// `sequelize.define` also returns the model
console.log(User === sequelize.models.User); // true

Extending Model

onst { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory');

class User extends Model {}

User.init({
  // Model attributes are defined here
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName: {
    type: DataTypes.STRING
    // allowNull defaults to true
  }
}, {
  // Other model options go here
  sequelize, // We need to pass the connection instance
  modelName: 'User' // We need to choose the model name
});

// the defined model is the class itself
console.log(User === sequelize.models.User); // true

내부적으로 sequelize.defineModel.init을 부른다. 때문에 위 두가지는 완전하게 같다.

Table name inference

위 두가지 방법 모두 테이블 이름(Users)이 명시적으로 정의되어 있지 않다. 그러나 model은 User라는 이름으로 정의되었다.

테이블이름이 주어지지 않는다면 Seqeulize는 default로 자동으로 model 이름의 복수형을 테이블 이름으로 사용한다. 이는 inflection이라는 라이브러리에서 동작하는데 person -> people 처럼 불규칙적인 복수형도 제대로 동작한다.

당연히 이런 동작 역시 configurable하다.

Enforcing the table name to be equal to the model name

freezeTableName: true 옵션을 통해서 auto-pluralization(자동 복수화)를 사용하지 않을 수 있다. 이 경우 Sequelize는 다른 수정없이 Model의 이름을 테이블의 이름으로 참조한다.

sequelize.define('User', {
  // ... (attributes)
}, {
  freezeTableName: true
});

위 예제는 User Model이 User 테이블을 가르키도록 한다.

이를 테이블 하나가 아닌 모든 테이블에 대해서 적용하고자 한다면 아래와 같이 sequelize instance를 생성 할 때 옵션을 설정하면 된다.

const sequelize = new Sequelize('sqlite::memory:', {
  define: {
    freezeTableName: true
  }
});

Providing the table name directly

테이블 이름을 아래와 같이 직접 명시 해줄 수도 있다.

sequelize.define('User', {
  // ... (attributes)
}, {
  tableName: 'Employees'
});

Model synchronization

Model을 정의 할 때, 테이블에 대한 몇 가지 정보를 Seqeulize에게 전달 할 수 있다. 그러나 데이터베이스에 해당 테이블이 존재하지 않는다면? 테이블은 존재하는데 다른 컬럼을 가지거나 실제 테이블과 다른 정보를 준다면?

이에 대한 해답은 model synchronization이다. Model은 model.sync(options)을 통해 데이터베이스와 동기화 될 수 있다. model.sync(options)은 Promise를 리턴하는 비동기 함수이다. 이를 호출함으로서 Seqeulize는 자동적으로 SQL 쿼리를 만들고 수행한다. 주의할 것은 이러한 변화는 데이터베이스 테이블에만 적용되며 JavaScript side의 Model에는 적용되지 않는다.

  • User.sync() - 테이블이 존재하지 않는다면 만든다.
  • User.sync({ force: true}) - 테이블이 존재한다면 drop하고 새로운 테이블을 만든다.
  • User.sync({ alter: true}) - 테이블의 현재 state(column, data type etc)를 체크하고 model과 비교했을 때 필요한 변경사항에 대해 수행한다.

Synchronizing all models at once

seqeulize.sync()를 통해 모든 Model에 대해 자동으로 동기화 할 수 있다.

await sequelize.sync({ force: true });
console.log("All models were synchronized successfully.");

Dropping tables

특정 테이블을 드랍

await User.drop();
console.log("User table dropped!");

모든 테이블 drop

await sequelize.drop();
console.log("All tables dropped!");

Database safety check

위에서 살펴본 syncdrop 오퍼레이션은 파괴적이다. Seqeulize는 정규표현식을 통해 선택적으로 연산이 가능하다록 match 연산을 제공한다.

// This will run .sync() only if database name ends with '_test'
sequelize.sync({ force: true, match: /_test$/ });

Synchronization in production

sync({ force: true })sync({ alter: true })는 매우 파괴적인 연산이다. 따라서 production level에서는 권장하지 않는다. 대신에 동기화는 Seqeulize CLI를 이용한 Migrations의 진보댄 개념으로 사용되어야 한다.

Timestamps

Sequelize는 모든 모델에 대해서 자동적으로 DataTypes.DATE형의 createdAt, updatedAt을 추가한다. 이 필드는 자동으로 관리된다.

Note: 이 필드들은 SQL trigger가 아니라 Seqeulize level에서 관리된다. 즉 Seqeulize 없이 직접적으로 SQL 쿼리를 날리는 경우에는 updatedAt필드는 업데이트 되지 않는다.

이는 timestamps: false 옵션을 통해 disable 할 수 있다.

sequelize.define('User', {
  // ... (attributes)
}, {
  timestamps: false
});

두개의 필드중 하나만 사용하는 것도 가능하며 custom 이름을 사용하는 것도 가능하다

class Foo extends Model {}
Foo.init({ /* attributes */ }, {
  sequelize,

  // don't forget to enable timestamps!
  timestamps: true,

  // I don't want createdAt
  createdAt: false,

  // I want updatedAt to actually be called updateTimestamp
  updatedAt: 'updateTimestamp'
});

Column declaration shorthand syntax

Column의 데이터타입만 정의하는 경우 아래와 같이 간단하게 표현이 가능하다.

/ This:
sequelize.define('User', {
  name: {
    type: DataTypes.STRING
  }
});

// Can be simplified to:
sequelize.define('User', { name: DataTypes.STRING });

Defatult Values

Seqeulize는 column의 default value를 NULL로 추정한다. 이는 defaultValue 옵션을 통해 설정 할 수 있다.

sequelize.define('User', {
  name: {
    type: DataTypes.STRING,
    defaultValue: "John Doe"
  }
});

Seqeulize.Now와 같은 스페셜 value 또한 가능하다.

sequelize.define('Foo', {
  bar: {
    type: DataTypes.DATETIME,
    defaultValue: Sequelize.NOW
    // This way, the current date/time will be used to populate this column (at the moment of insertion)
  }
});

Data Types

Model의 정의된 모든 Column은 data type을 가진다. Seqeulize는 많은 수의 built-in data types를 제공한다.
built-in data types에 접근하기 위해서는 DataTypes를 import해야한다.

const { DataTypes } = require("sequelize"); // Import the built-in data types
Strings

Strings

DataTypes.STRING             // VARCHAR(255)
DataTypes.STRING(1234)       // VARCHAR(1234)
DataTypes.STRING.BINARY      // VARCHAR BINARY
DataTypes.TEXT               // TEXT
DataTypes.TEXT('tiny')       // TINYTEXT
DataTypes.CITEXT             // CITEXT          PostgreSQL and SQLite only.

Boolean

DataTypes.BOOLEAN            // TINYINT(1)

Numbers

DataTypes.INTEGER            // INTEGER
DataTypes.BIGINT             // BIGINT
DataTypes.BIGINT(11)         // BIGINT(11)

DataTypes.FLOAT              // FLOAT
DataTypes.FLOAT(11)          // FLOAT(11)
DataTypes.FLOAT(11, 10)      // FLOAT(11,10)

DataTypes.REAL               // REAL            PostgreSQL only.
DataTypes.REAL(11)           // REAL(11)        PostgreSQL only.
DataTypes.REAL(11, 12)       // REAL(11,12)     PostgreSQL only.

DataTypes.DOUBLE             // DOUBLE
DataTypes.DOUBLE(11)         // DOUBLE(11)
DataTypes.DOUBLE(11, 10)     // DOUBLE(11,10)

DataTypes.DECIMAL            // DECIMAL
DataTypes.DECIMAL(10, 2)     // DECIMAL(10,2)

Unsigned & Zerofill integers - MySQL/MariaDB only

DataTypes.INTEGER.UNSIGNED
DataTypes.INTEGER.ZEROFILL
DataTypes.INTEGER.UNSIGNED.ZEROFILL
// You can also specify the size i.e. INTEGER(10) instead of simply INTEGER
// Same for BIGINT, FLOAT and DOUBLE

Dates

DataTypes.DATE       // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
DataTypes.DATE(6)    // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
DataTypes.DATEONLY   // DATE without time

UUIDS

{
  type: DataTypes.UUID,
  defaultValue: Sequelize.UUIDV4 // Or Sequelize.UUIDV1
}

Ohters

다른 데이터 타입에 대해서는 seperate guid를 참고하자

Column Options

column의 타입을 정할 때 allowNull, defaultValue와 같은 옵션들을 적용 할 수 있다. 아래의 몇 가지 example이 있다.

const { Model, DataTypes, Deferrable } = require("sequelize");

class Foo extends Model {}
Foo.init({
  // instantiating will automatically set the flag to true if not set
  flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },

  // default values for dates => current time
  myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },

  // setting allowNull to false will add NOT NULL to the column, which means an error will be
  // thrown from the DB when the query is executed if the column is null. If you want to check that a value
  // is not null before querying the DB, look at the validations section below.
  title: { type: DataTypes.STRING, allowNull: false },

  // Creating two objects with the same value will throw an error. The unique property can be either a
  // boolean, or a string. If you provide the same string for multiple columns, they will form a
  // composite unique key.
  uniqueOne: { type: DataTypes.STRING,  unique: 'compositeIndex' },
  uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' },

  // The unique property is simply a shorthand to create a unique constraint.
  someUnique: { type: DataTypes.STRING, unique: true },

  // Go on reading for further information about primary keys
  identifier: { type: DataTypes.STRING, primaryKey: true },

  // autoIncrement can be used to create auto_incrementing integer columns
  incrementMe: { type: DataTypes.INTEGER, autoIncrement: true },

  // You can specify a custom column name via the 'field' attribute:
  fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' },

  // It is possible to create foreign keys:
  bar_id: {
    type: DataTypes.INTEGER,

    references: {
      // This is a reference to another model
      model: Bar,

      // This is the column name of the referenced model
      key: 'id',

      // With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type.
      deferrable: Deferrable.INITIALLY_IMMEDIATE
      // Options:
      // - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints
      // - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction
      // - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction
    }
  },

  // Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL
  commentMe: {
    type: DataTypes.INTEGER,
    comment: 'This is a column name that has a comment'
  }
}, {
  sequelize,
  modelName: 'foo',

  // Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options:
  indexes: [{ unique: true, fields: ['someUnique'] }]
});

Taking advantage of Models being classes

Sequelize model은 ES6 classes이다. 때문에 class level methods, custom instance를 쉽게 추가 할 수 있다.

class User extends Model {
  static classLevelMethod() {
    return 'foo';
  }
  instanceLevelMethod() {
    return 'bar';
  }
  getFullname() {
    return [this.firstname, this.lastname].join(' ');
  }
}
User.init({
  firstname: Sequelize.TEXT,
  lastname: Sequelize.TEXT
}, { sequelize });

console.log(User.classLevelMethod()); // 'foo'
const user = User.build({ firstname: 'Jane', lastname: 'Doe' });
console.log(user.instanceLevelMethod()); // 'bar'
console.log(user.getFullname()); // 'Jane Doe'
profile
어려운 문제를 함께 풀어가는 것을 좋아합니다.

0개의 댓글