javascript - Sequelize 动态播种

标签 javascript sequelize.js seeding

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

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

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

用户.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, {});
  }
};

配置文件.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两者都循环(不理想)。

最佳答案

警告:在使用 sequelize 一年多之后,我开始意识到我的建议是一个非常糟糕的做法。我会在底部解释。

tl;博士:

  • 从不使用播种机,只使用迁移
  • 永远不要在迁移中使用您的 Sequelize 模型,只编写显式 SQL

  • 我的另一个建议仍然是你使用一些“配置”来驱动种子数据的生成。 (但是应该通过迁移插入种子数据。)

    vv 不要这样做 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');
        }
    }
    

    这是部分实现。然而,它省略了一些关键细节,因为有上百万种方法可以给那只猫剥皮。这些部分都可以集中在“配置”标题下,我们现在应该谈论它。

    生成种子数据时,通常有以下要求:
  • 我想创建至少一百个,或者
  • 我希望它们的属性从可接受的集合中随机确定,或者
  • 我想创建一个形状完全像这样的关系网

  • 您可以尝试将这些内容硬编码到您的算法中,但这是困难的方法。我喜欢做的是在播种器的顶部声明“配置”,以捕获所需种子数据的骨架。然后,在元组生成函数中,我使用该配置以程序方式生成实际行。显然,您可以根据自己的喜好表达该配置。我试着把它全部放在一个 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: 3Driver.findAll , 和 limit: 4Car.findAll .然后它会分配一个真实的、随机选择的 Car实例到每个唯一的字符串。最后,在生成关联元组时,它使用字符串查找选择的 Car从中提取外键和其他值。

    毫无疑问,有更奇特的方法可以为种子数据指定模板。随心所欲地给那只猫剥皮。希望这能让您清楚地了解如何将您选择的算法与实际的 sqlz 实现结合起来,以生成一致的种子数据。

    为什么上面的不好

    如果您在迁移或播种器文件中使用您的 sequelize 模型,您将不可避免地创建应用程序无法从干净的石板成功构建的情况。

    如何避免疯狂:
  • 永远不要使用播种机,只使用迁移

  • (你可以在播种机中做的任何事情,你都可以在迁移中做。当我列举播种机的问题时请记住这一点,因为这意味着这些问题都没有给你带来任何好处。)

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

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

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

  • 当您使用 sequelize 模型获取记录时,它会显式获取它知道的每一列。所以,想象一下这样的迁移序列:
  • M1:创建表 Car & Driver
  • M2:使用Car & Driver模型生成种子数据

  • 那可行。快进到向 Car 添加新列的日期(例如 isElectric )。这包括:(1) 创建一个迁移来添加列,以及 (2) 在 sequelize 模型上声明新列。现在您的迁移过程如下所示:
  • M1:创建表 Car & Driver
  • M2:使用Car & Driver模型生成种子数据
  • M3:添加 isElectric到车

  • 问题是你的 Sequelize 模型总是反射(reflect)最终的模式,而没有承认实际数据库是通过有序的突变增加来构建的。因此,在我们的示例中,M2 将失败,因为任何内置选择方法(例如 Car.findOne)都会执行如下 SQL 查询:
    SELECT
      "Car"."make" AS "Car.make",
      "Car"."isElectric" AS "Car.isElectric"
    FROM
      "Car"
    

    您的数据库会抛出异常,因为 Car 没有 isElectric M2 执行时的列。

    该问题不会发生在仅落后于一次迁移的环境中,但如果您聘请新开发人员或在本地工作站上核对数据库并从头开始构建应用程序,您就会受到影响。

    关于javascript - Sequelize 动态播种,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43151613/

    相关文章:

    javascript - Extjs 4.没有显式 ID 的 location.hash

    node.js - sequelize.js - 如何从 sequelize 生成的查询中删除双引号

    node.js - 使用 Sequelize for Node JS 将 where 子句添加到连接表时遇到问题

    asp.net-core - ASP.NET Core 2.2 创建身份用户

    ruby-on-rails - 避免通过 seeds.rb 创建重复记录?

    ruby-on-rails - rails 数据库 :seed error "undefined method ` finder_needs_type_condition ?' for nil:NilClass"

    javascript - D3 图表问题,数据绑定(bind)不起作用

    javascript - 访问 ionic 项目中自定义 cordova 插件的项目资源

    javascript - 如何使用纯JavaScript判断Facebook/Twitter是否被屏蔽?

    node.js - 未处理的拒绝 SequelizeDatabaseError : type "enum_*" already exists