ゲッター、セッター、および仮想属性
Sequelizeでは、モデルの属性にカスタムゲッターとセッターを定義できます。
Sequelizeでは、いわゆる_仮想属性_を指定することもできます。これは、基盤となるSQLテーブルには実際には存在しないSequelizeモデルの属性ですが、Sequelizeによって自動的に設定されます。たとえば、コードを簡素化できるカスタム属性を作成するのに非常に役立ちます。
ゲッター
ゲッターは、モデル定義の1つの列に定義された`get()`関数です。
const User = sequelize.define('user', {
  // Let's say we wanted to see every username in uppercase, even
  // though they are not necessarily uppercase in the database itself
  username: {
    type: DataTypes.STRING,
    get() {
      const rawValue = this.getDataValue('username');
      return rawValue ? rawValue.toUpperCase() : null;
    },
  },
});
このゲッターは、標準のJavaScriptゲッターと同様に、フィールド値が読み取られると自動的に呼び出されます。
const user = User.build({ username: 'SuperUser123' });
console.log(user.username); // 'SUPERUSER123'
console.log(user.getDataValue('username')); // 'SuperUser123'
上記では「SUPERUSER123」がログに記録されましたが、データベースに実際に格納されている値は依然として「SuperUser123」であることに注意してください。 `this.getDataValue('username')`を使用してこの値を取得し、大文字に変換しました。
代わりにゲッターで`this.username`を使用しようとすると、無限ループが発生します。そのため、Sequelizeは`getDataValue`メソッドを提供しています。
セッター
セッターは、モデル定義の1つの列に定義された`set()`関数です。設定される値を受け取ります。
const User = sequelize.define('user', {
  username: DataTypes.STRING,
  password: {
    type: DataTypes.STRING,
    set(value) {
      // Storing passwords in plaintext in the database is terrible.
      // Hashing the value with an appropriate cryptographic hash function is better.
      this.setDataValue('password', hash(value));
    },
  },
});
const user = User.build({
  username: 'someone',
  password: 'NotSo§tr0ngP4$SW0RD!',
});
console.log(user.password); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc'
console.log(user.getDataValue('password')); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc'
Sequelizeは、データベースにデータを送信する前でさえ、セッターを自動的に呼び出したことに注意してください。データベースが認識したデータは、すでにハッシュされた値だけでした。
計算にモデルインスタンスの別のフィールドを含めたい場合は、それも可能で非常に簡単です。
const User = sequelize.define('user', {
  username: DataTypes.STRING,
  password: {
    type: DataTypes.STRING,
    set(value) {
      // Storing passwords in plaintext in the database is terrible.
      // Hashing the value with an appropriate cryptographic hash function is better.
      // Using the username as a salt is better.
      this.setDataValue('password', hash(this.username + value));
    },
  },
});
**注意:**パスワード処理に関する上記の例は、パスワードをプレーンテキストで保存するよりもはるかに優れていますが、完璧なセキュリティからはほど遠いものです。パスワードを適切に処理することは難しいため、ここで示したものは、Sequelizeの機能を示すための例にすぎません。サイバーセキュリティの専門家に相談するか、OWASPのドキュメントを読むか、InfoSec StackExchangeにアクセスすることをお勧めします。
ゲッターとセッターの組み合わせ
ゲッターとセッターは、同じフィールドに定義できます。
例として、`content`が長さ無制限のテキストである`Post`をモデリングしているとします。メモリ使用量を改善するために、コンテンツのgzipバージョンを保存したいとします。
注:最新のデータベースは、このような場合、自動的に圧縮を実行するはずです。これはあくまでも例として示したものであることに注意してください。
const { gzipSync, gunzipSync } = require('zlib');
const Post = sequelize.define('post', {
  content: {
    type: DataTypes.TEXT,
    get() {
      const storedValue = this.getDataValue('content');
      const gzippedBuffer = Buffer.from(storedValue, 'base64');
      const unzippedBuffer = gunzipSync(gzippedBuffer);
      return unzippedBuffer.toString();
    },
    set(value) {
      const gzippedBuffer = gzipSync(value);
      this.setDataValue('content', gzippedBuffer.toString('base64'));
    },
  },
});
上記のセットアップでは、`Post`モデルの`content`フィールドを操作しようとすると、Sequelizeはカスタムゲッターとセッターを自動的に処理します。たとえば、
const post = await Post.create({ content: 'Hello everyone!' });
console.log(post.content); // 'Hello everyone!'
// Everything is happening under the hood, so we can even forget that the
// content is actually being stored as a gzipped base64 string!
// However, if we are really curious, we can get the 'raw' data...
console.log(post.getDataValue('content'));
// Output: 'H4sIAAAAAAAACvNIzcnJV0gtSy2qzM9LVQQAUuk9jQ8AAAA='
仮想フィールド
仮想フィールドは、Sequelizeが内部で設定するフィールドですが、実際にはデータベースには存在しません。
たとえば、ユーザーの`firstName`属性と`lastName`属性があるとします。
繰り返しますが、これはあくまでも例として示したものです。
_フルネーム_を直接取得する簡単な方法があれば便利です。 `ゲッター`のアイデアと、Sequelizeがこの種の状況に提供する特別なデータ型である`DataTypes.VIRTUAL`を組み合わせることができます。
const { DataTypes } = require('sequelize');
const User = sequelize.define('user', {
  firstName: DataTypes.TEXT,
  lastName: DataTypes.TEXT,
  fullName: {
    type: DataTypes.VIRTUAL,
    get() {
      return `${this.firstName} ${this.lastName}`;
    },
    set(value) {
      throw new Error('Do not try to set the `fullName` value!');
    },
  },
});
`VIRTUAL`フィールドは、テーブルに列を作成しません。つまり、上記のモデルには`fullName`列がありません。ただし、存在するように見えます。
const user = await User.create({ firstName: 'John', lastName: 'Doe' });
console.log(user.fullName); // 'John Doe'
非推奨: `getterMethods`と`setterMethods`
この機能はSequelize 7で削除されました。代わりに、VIRTUAL属性またはネイティブクラスのゲッターとセッターを使用することを検討してください。
Sequelizeは、モデル定義で`getterMethods`オプションと`setterMethods`オプションも提供して、仮想属性のように見えるが、実際には同じではないものを指定します。
例
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define(
  'user',
  {
    firstName: DataTypes.STRING,
    lastName: DataTypes.STRING,
  },
  {
    getterMethods: {
      fullName() {
        return this.firstName + ' ' + this.lastName;
      },
    },
    setterMethods: {
      fullName(value) {
        // Note: this is just for demonstration.
        // See: https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/
        const names = value.split(' ');
        const firstName = names[0];
        const lastName = names.slice(1).join(' ');
        this.setDataValue('firstName', firstName);
        this.setDataValue('lastName', lastName);
      },
    },
  },
);
(async () => {
  await sequelize.sync();
  let user = await User.create({ firstName: 'John', lastName: 'Doe' });
  console.log(user.fullName); // 'John Doe'
  user.fullName = 'Someone Else';
  await user.save();
  user = await User.findOne();
  console.log(user.firstName); // 'Someone'
  console.log(user.lastName); // 'Else'
})();