Sequelize动态播种

时间:2017-04-01 00:55:42

标签: javascript sequelize.js seeding

我目前正在使用Sequelize.js播种数据并使用关键ID的硬编码值。这不理想,因为我真的应该能够动态地做到这一点吗?例如,将用户和配置文件与“拥有一个”和“属于”关联相关联。我不一定希望使用硬编码的profileId来播种用户。在创建配置文件后,我宁愿在配置文件种子中执行此操作。创建配置文件后,动态地将profileId添加到用户。使用Sequelize.js时,这是否可行?或者,在使用Sequelize播种时,只需硬编码关联ID就更常见了吗?

也许我正在播种错误?我是否应该使用Sequelize使用一对一的种子文件和迁移文件?在Rails中,通常只有1个种子文件,如果需要,您可以选择分成多个文件。

一般来说,只是在这里寻找指导和建议。这些是我的文件:

users.js

// User seeds

'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
    /*
      Add altering commands here.
      Return a promise to correctly handle asynchronicity.

      Example:
      return queryInterface.bulkInsert('Person', [{
        name: 'John Doe',
        isBetaMember: false
      }], {});
    */

    var users = [];
    for (let i = 0; i < 10; i++) {
      users.push({
        fname: "Foo",
        lname: "Bar",
        username: `foobar${i}`,
        email: `foobar${i}@gmail.com`,
        profileId: i + 1
      });
    }
    return queryInterface.bulkInsert('Users', users);
  },

  down: function (queryInterface, Sequelize) {
    /*
      Add reverting commands here.
      Return a promise to correctly handle asynchronicity.

      Example:
      return queryInterface.bulkDelete('Person', null, {});
    */
    return queryInterface.bulkDelete('Users', null, {});
  }
};

profiles.js

// Profile seeds

'use strict';
var models = require('./../models');
var User = models.User;
var Profile = models.Profile;


module.exports = {
  up: function (queryInterface, Sequelize) {
    /*
      Add altering commands here.
      Return a promise to correctly handle asynchronicity.

      Example:
      return queryInterface.bulkInsert('Person', [{
        name: 'John Doe',
        isBetaMember: false
      }], {});
    */

    var profiles = [];
    var genders = ['m', 'f'];
    for (let i = 0; i < 10; i++) {
      profiles.push({
        birthday: new Date(),
        gender: genders[Math.round(Math.random())],
        occupation: 'Dev',
        description: 'Cool yo',
        userId: i + 1
      });
    }
    return queryInterface.bulkInsert('Profiles', profiles);
  },

  down: function (queryInterface, Sequelize) {
    /*
      Add reverting commands here.
      Return a promise to correctly handle asynchronicity.

      Example:
      return queryInterface.bulkDelete('Person', null, {});
    */
    return queryInterface.bulkDelete('Profiles', null, {});
  }
};

正如您所看到的,我只是使用硬编码for循环(非理想)。

2 个答案:

答案 0 :(得分:4)

您可以使用续集create-with-association功能将它们组合在一个文件中,而不是为用户和配置文件使用不同的种子。

另外,当使用一系列create()时,你必须将它们包装在Promise.all()中,因为种子接口需要一个Promise作为返回值。

up: function (queryInterface, Sequelize) {
  return Promise.all([
    models.Profile.create({
        data: 'profile stuff',
        users: [{
          name: "name",
          ...
        }, {
          name: 'another user',
          ...
        }]}, {
        include: [ model.users]
      }
    ),
    models.Profile.create({
      data: 'another profile',
      users: [{
        name: "more users",
        ...
      }, {
        name: 'another user',
        ...
      }]}, {
        include: [ model.users]
      }
    )
  ])
}

不确定这是否真的是最好的解决方案,但这就是我如何在种子文件中自行维护外键。

答案 1 :(得分:2)

警告:在使用sequelize超过一年后,我已经意识到我的建议是一个非常糟糕的做法。我将在底部解释。

TL; DR:

  1. 从不使用播种机,只使用迁移
  2. 永远不要在迁移中使用续集模型,只编写显式SQL
  3. 我的另一个建议仍然是你使用了一些&#34;配置&#34;驱动种子数据的生成。 (但应通过迁移插入种子数据。)

    vv请勿执行此操作

    这是我喜欢的另一种模式,因为我相信它更灵活,更容易被理解。我在这里提供它作为已接受答案的替代方案(对我来说似乎没问题,顺便说一句),以防其他人发现它更适合他们的情况。

    策略是利用您已经定义的sqlz模型来获取由其他播种者创建的数据,使用该数据生成您想要的任何新关联,然后使用bulkInsert插入新行。

    在这个例子中,我跟踪一组人和他们拥有的汽车。我的模特/桌子:

    • Driver:一个可能拥有一辆或多辆真车的真人
    • Car:不是特定的汽车,而是可由某人拥有的类型汽车(即make + model
    • DriverCar:一个真人拥有的真车,颜色和他们买的一年

    我们假设以前的播种机已经在数据库中存储了所有已知的Car类型:该信息已经可用,我们不希望在我们将数据捆绑在一起时给用户带来不必要的数据输入系统。我们还假设已经有Driver行,通过播种或系统正在使用中。

    目标是以自动方式从这两个数据源生成一大堆虚假但似乎合理的DriverCar关系。

    const {
        Driver,
        Car
    } = require('models')
    
    module.exports = {
    
        up: async (queryInterface, Sequelize) => {
    
            // fetch base entities that were created by previous seeders
            // these will be used to create seed relationships
    
            const [ drivers , cars ] = await Promise.all([
                Driver.findAll({ /* limit ? */ order: Sequelize.fn( 'RANDOM' ) }),
                Car.findAll({ /* limit ? */ order: Sequelize.fn( 'RANDOM' ) })
            ])
    
            const fakeDriverCars = Array(30).fill().map((_, i) => {
                // create new tuples that reference drivers & cars,
                // and which reflect the schema of the DriverCar table
            })
    
            return queryInterface.bulkInsert( 'DriverCar', fakeDriverCars );
        },
    
        down: (queryInterface, Sequelize) => {
            return queryInterface.bulkDelete('DriverCar');
        }
    }
    

    这是部分实施。但是,它省略了一些关键的细节,因为有一百万种方法可以给那只猫皮肤。这些作品都可以在标题&#34;配置下收集,&#34;我们现在应该谈谈它。

    生成种子数据时,通常会有以下要求:

    • 我想创建至少一百个,或
    • 我希望从可接受的集合中随机确定其属性,或
    • 我想创建一个与 this
    • 完全相同的关系网

    您可以尝试将这些内容硬编码到您的算法中,但这很难。我喜欢做的是声明&#34;配置&#34;在播种机的顶部,捕获所需种子数据的骨架。然后,在元组生成函数中,我使用该配置来程序生成实际行。显然,您可以表达该配置。我尝试将它全部放入一个CONFIG对象中,以便它们保持在一起,这样我就可以轻松找到播种器实现中的所有引用。

    您的配置可能意味着limit来电的findAll值合理。它还可能指定应该用于计算要生成的种子行数的所有因子(通过明确说明quantity: 30或通过组合算法)。

    作为深思熟虑,这是一个非常简单配置的示例,我使用此DriverCar系统确保我有2个驱动程序,每个驱动程序拥有一个重叠的汽车(具有特定的汽车到在运行时随机选择):

    const CONFIG = {
        ownership: [
            [ 'a', 'b', 'c', 'd' ], // driver 1 linked to cars a, b, c, and d
            [ 'b' ],                // driver 2 linked to car b
            [ 'b', 'b' ]            // driver 3 has two of the same kind of car
        ]
    };
    

    我实际上也使用过那些字母。在运行时,播种器实现将确定仅需要3个唯一Driver行和4个唯一Car行,并将limit: 3应用于Driver.findAlllimit: 4Car.findAll。然后它会为每个唯一字符串分配一个真实的,随机选择的Car实例。最后,在生成关联元组时,它使用字符串查找从中提取外键和其他值的所选Car

    毫无疑问,为种子数据指定模板的方式更为出色。不管你喜欢哪种猫皮。希望这清楚地说明了如何将您选择的算法与实际的sqlz实现相结合以生成连贯的种子数据。

    为什么以上不好

    如果您在迁移或播种器文件中使用续集模型,则不可避免地会产生一种情况,即应用程序无法从干净的平板中成功构建。

    如何避免疯狂:

    1. 绝不使用播种机,仅使用迁移
    2. (你可以在播种机上做任何事情,你可以在迁移中做。记住这一点,因为我列举了播种机的问题,因为这意味着这些问题都不会给你带来任何好处。)

      默认情况下,sequelize不会记录已播放的播种机的记录。是的,您可以对其进行配置以保留记录,但如果已经在没有该设置的情况下部署了应用,那么当您使用新设置部署应用时,它仍然会最后一次重新运行所有播种机。如果这不安全,您的应用会爆炸。我的经验是,种子数据不能复制,如果它不立即违反唯一性约束,它就会创建重复的行。

      运行播种机是一个单独的命令,然后您需要将其集成到启动脚本中。这很容易导致npm脚本的激增,这使得app启动更难以遵循。在一个项目中,我将仅有的2个播种器转换为迁移,并将启动相关的npm脚本数量从13个减少到5个。

      很难确定,但很难理解播种机的运行顺序。还要记住,命令对于运行迁移和播种器是分开的,这意味着您无法有效地交错它们。您必须先运行所有迁移,然后运行所有播种机。随着数据库随时间的变化,您将遇到我接下来描述的问题:

      1. 切勿在迁移中使用续集模型
      2. 当您使用续集模型来获取记录时,它会显式提取它所知道的每一列。所以,想象一下像这样的迁移序列:

        • M1:创建表格Car&amp;驱动程序
        • M2:使用Car&amp;用于生成种子数据的驱动程序模型

        那会有效。快进到向Car添加新列的日期(例如isElectric)。这包括:(1)创建一个migraiton来添加列,以及(2)在sequelize模型上声明新列。现在您的迁移过程如下所示:

        • M1:创建表格Car&amp;驱动程序
        • M2:使用Car&amp;用于生成种子数据的驱动程序模型
        • M3:将isElectric添加到Car

        问题是你的续集模型总是反映最终的模式,而不承认实际的数据库是通过有序的突变增加来构建的。因此,在我们的示例中,M2将失败,因为任何内置选择方法(例如Car.findOne)将执行SQL查询,如:

        SELECT
          "Car"."make" AS "Car.make",
          "Car"."isElectric" AS "Car.isElectric"
        FROM
          "Car"
        

        你的数据库会抛出,因为当M2执行时,汽车没有isElectric列。

        问题不会发生在只有一次迁移的环境中,但如果你雇佣一名新开发人员或在本地工作站上核对数据库并从头开始构建应用程序,那么你就会受到影响。