rosieとfaker.jsを使ってObjection.jsのModelを楽に生成する

公開日:2019-08-12
最終更新:2019-08-12
※この記事は外部サイト(https://qiita.com/uki00a/items/950a54e0894...)からのクロス投稿です

やりたいこと

テストコード等で、rosiefaker.jsを使用して、Objection.jsのModelの生成を楽にすることが目的です。

  const room = await factories.room.save({  
    members: [factories.user.build()],  
    messages: [factories.message.build()]  
  });  
  const message = await factories.message.save({ room: factories.room.build() });  
  const user = await factories.user.save();    

依存モジュール

テーブル定義

事前に、以下のようなテーブルが定義されている想定です。

async function createTables(knex) {  
  await knex.schema.createTable('users', t => {  
    t.increments('id').primary().notNullable();  
    t.string('name').notNullable();  
    t.string('email').notNullable();  
  });  

  await knex.schema.createTable('rooms', t => {  
    t.increments('id').primary().notNullable();  
    t.string('name').notNullable();  
  });  

  await knex.schema.createTable('room_members', t => {  
    t.integer('room_id').notNullable();     
    t.integer('user_id').notNullable();  

    t.foreign('room_id').references('id').inTable('rooms');  
    t.foreign('user_id').references('id').inTable('users');  

    t.primary(['room_id', 'user_id']);  
  });  

  await knex.schema.createTable('messages', t => {  
    t.increments('id').primary().notNullable();  
    t.string('text').notNullable();  
    t.integer('room_id').notNullable();  

    t.foreign('room_id').references('id').inTable('rooms');  
  });  
}  

セットアップ

依存モジュールの読み込み・設定

const { Model, snakeCaseMappers, transaction } = require('objection');  
const Knex = require('knex');  
const { Factory } = require('rosie');  
const faker = require('faker');  

const dbConfig = { ... };  
const knex = Knex(dbConfig);  
Model.knex(knex);  

Modelの定義

class BaseModel extends Model {  
  static get columnNameMappers() {  
    return snakeCaseMappers();  
  }  
}  

class User extends BaseModel {  
  static get tableName() {  
    return 'users';  
  }  

  static get jsonSchema() {  
    return {  
      type: 'object',  
      required: ['name', 'email'],  
      properties: {  
        id: { type: 'integer' },  
        name: { type: 'string' },  
        email: { type: 'string' }  
      }  
    };  
  }  
}  

class Room extends BaseModel {  
  static get tableName() {  
    return 'rooms';  
  }  

  static get jsonSchema() {  
    return {  
      type: 'object',  
      required: ['name'],  
      properties: {  
        id: { type: 'integer' },  
        name: { type: 'string' }  
      }  
    };  
  }  

  static get relationMappings() {  
    return {  
      messages: {  
        relation: Model.HasManyRelation,  
        modelClass: Message,  
        join: {  
          from: 'rooms.id',  
          to: 'messages.room_id'  
        }  
      },  
      members: {  
        relation: Model.ManyToManyRelation,  
        modelClass: User,  
        join: {  
          from: 'rooms.id',  
          to: 'users.id',  
          through: {  
            from: 'room_members.room_id',   
            to: 'room_members.user_id'  
          }  
        }  
      }  
    };  
  }  
}  

class Message extends BaseModel {  
  static get tableName() {  
    return 'messages';  
  }  

  static get relationMappings() {  
    return {  
      room: {  
        relation: Model.BelongsToOneRelation,  
        modelClass: Room,  
        join: {  
          from: 'messages.room_id',  
          to: 'rooms.id'  
        }  
      }  
    };  
  }  

  static get jsonSchema() {  
    return {  
      type: 'object',  
      required: ['text'],  
      properties: {  
        id: { type: 'number' },  
        text: { type: 'string' }  
      }  
    };  
  }  
}  

ヘルパー関数を用意する

function defineModelFactory(Model, defaultAttributes) {  
  function Wrapper(attributes) {  
    return Model.fromJson(attributes);  
  }  
  const factory = Factory.define(Model.tableName, Wrapper);  

  for (const key of Object.keys(defaultAttributes)) {  
    factory.attr(key, defaultAttributes[key]);  
  }  

  return {  
    // インスタンスの生成のみ  
    build: (override = {}) => factory.build(override),  
    // インスタンスの生成及び永続化を行う  
    save: async (override = {}) => {  
      const model = factory.build(override)  
      await transaction(Model.knex(), trx => {  
        return model.$query(trx).insertGraph(); // `insertGraph`はatomicではないので、明示的にトランザクションオブジェクトを渡しています。  
      });  
      return model;  
    }  
  };  
}  

const emptyArray = () => [];  
const factories = {  
  user: defineModelFactory(User, {  
    name: faker.name.findName,   
    email: faker.internet.email  
  }),  
  message: defineModelFactory(Message, {  
    text: faker.random.word,  
    room: null  
  }),  
  room: defineModelFactory(Room, {  
    name: faker.random.word,  
    members: emptyArray,  
    messages: emptyArray  
  })  
};  

使い方

  const room = await factories.room.save({  
    members: [factories.user.build()],  
    messages: [factories.message.build()]  
  });  
  const room = await Room.query().eager('[members, messages]').first();  
  console.log(room);  
  // Room {  
  //   id: 1,  
  //   name: 'Ergonomic',  
  //   members:  
  //    [ User { id: 1, name: 'Napoleon Grady III', email: '[email protected]' } ],  
  // messages: [ Message { id: 1, text: '24/7', roomId: 1 } ] }  


  const message = await factories.message.save({room: factories.room.build()});  
  console.log(await Message.query().findById(message.id));  
  // Message { id: 2, text: 'programming', roomId: 2 }  

  const user = await factories.user.save();    
  console.log(await User.query().findById(user.id));  
  // User {  
  //   id: 2,  
  //   name: 'Justine Marquardt',  
  //   email: '[email protected]' }  

参考

記事が少しでもいいなと思ったらクラップを送ってみよう!
0
+1
@uki00aの技術ブログ

よく一緒に読まれている記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる

技術ブログをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

技術ブログを開設する

Qrunchでアウトプットをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

Markdownで書ける

ログ機能でアウトプットを加速

デザインのカスタマイズが可能

技術ブログ開設

ここから先はアカウント(ブログ)開設が必要です

英数字4文字以上
.qrunch.io
英数字6文字以上
ログインする