node.js - Sequelize 和羽毛 : When Relationships Fall Apart

标签 node.js postgresql sequelize.js feathersjs vorpal.js

在尝试弄清楚为什么我的 Sequelize 模型不致力于他们的关系两天之后,我决定是时候向大家征求意见了。

这是故事。

我正在使用以 Sequelize 作为驱动程序的 Postgres (9.4) 数据库编写 Feathers JS 应用程序。我在 Feathers Docs 中完成了设置,经过一番劝说,我开始运行迁移。

据我所知,必须特别考虑使用 Sequelize 获得双向关系,因为如果 ModelA 引用 ModelBModelB必须已经定义,但是如果 ModelB 引用 ModelA...那么,我们就会遇到依赖循环。

正是由于文档所说的“使用此处描述的方法定义您的模型”的依赖循环。 (好吧,从技术上讲,它只是“假设”使用了这样的结构。另外,我只能发布 2 个链接,否则我会链接那个傻瓜。对此感到抱歉。)我在 Feathers demo 中找到了相同的结构。 .

自然地,我反射(reflect)了所有这些(当然,除非我遗漏了一个小但重要的细节),但是......仍然没有骰子。

这是我正在查看的内容:

迁移

migrations/create-accounts.js

'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
    // Make the accounts table if it doesn't already exist.
    // "If it doesn't already exist" because we have the previous migrations
    //  from Laravel.
    return queryInterface.showAllTables().then(function(tableNames) {
      if (tableNames.accounts === undefined) {
        queryInterface.createTable('accounts', {
          // Field definitions here
          id: {
            type: Sequelize.INTEGER,
            primaryKey: true,
            autoIncrement: true
          },
          name: Sequelize.STRING,
          url_name: Sequelize.STRING,
          createdAt: {
            type: Sequelize.DATE,
            allowNull: false
          },
          updatedAt: {
            type: Sequelize.DATE,
            allowNull: false
          },
          deletedAt: Sequelize.DATE
        });
      }
    });

    // See the create-user migration for an explanation of why I
    //  commented out the above code.
  },

  down: function (queryInterface, Sequelize) {
    return queryInterface.dropTable('accounts');
  }
};

migrations/create-users.js

'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
    return queryInterface.showAllTables().then(function(tableNames) {
      if (tableNames.users === undefined) {
        queryInterface.createTable('users', {
          id: {
            type: Sequelize.INTEGER,
            primaryKey: true,
            autoIncrement: true
          },
          accountId: {
            type: Sequelize.INTEGER,
            references: {
              model: 'accounts',
              key: 'id'
            },
            allowNull: false
          },
          email: {
            type: Sequelize.STRING,
            allowNull: false
          },
          [...]
        });
      }
    });
  },

  down: function (queryInterface, Sequelize) {
    return queryInterface.dropTable('users');
  }
};

psql

然后我启动了 psql 来查看引用是否正确:

databaseName=#\d accounts:

Referenced by:
    TABLE "users" CONSTRAINT "users_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES accounts(id)

databaseName=#\d 用户:

Foreign-key constraints:
    "users_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES accounts(id)

到目前为止还不错吧?

让我们看看这个程序的模型部分!

模型

src/models/account.js

'use strict';

// account-model.js - A sequelize model
//
// See http://docs.sequelizejs.com/en/latest/docs/models-definition/
// for more of what you can do here.

const Sequelize = require('sequelize');

module.exports = function(app) {
  // We assume we're being called from app.configure();
  // If we're not, though, we need to be passed the app instance.
  // Fair warning: I added this bit myself, so it's suspect.
  if (app === undefined)
    app = this;
  const sequelize = app.get('sequelize');

  // The rest of this is taken pretty much verbatim from the examples
  const account = sequelize.define('account', {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    name: Sequelize.STRING,
    url_name: Sequelize.STRING,
  }, {
    paranoid: true,
    timestamps: true,

    classMethods: {
      associate() {
        const models = app.get('models');
        this.hasMany(models['user'], {});
      }
    }
  });

  return account;
};

src/models/user.js

'use strict';

// user-model.js - A sequelize model
//
// See http://docs.sequelizejs.com/en/latest/docs/models-definition/
// for more of what you can do here.

const Sequelize = require('sequelize');

module.exports = function(app) {
  // We assume we're being called from app.configure();
  // If we're not, though, we need to be passed the app instance
  if (app === undefined)
    app = this;
  const sequelize = app.get('sequelize');

  const user = sequelize.define('user', {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    accountId: {
      type: Sequelize.INTEGER,
      references: {
        model: 'accounts', // Table name...is that right? Made the migration work...
        key: 'id'
      }
    },
    email: Sequelize.STRING,
    [... curtailed for brevity ...]
  }, {
    // Are these necessary here, or just when defining the model to make a
    //  psuedo-migration?
    paranoid: true, // soft deletes
    timestamps: true,

    classMethods: {
      associate() {
        const models = app.get('models');
        // This outputs like I'd expect:
        // Just to be sure...From the user model, models["account"]: account
        console.log('Just to be sure...From the user model, models["account"]:', models['account']);
        this.belongsTo(models['account'], {});
      }
    }
  });

  return user;
};

src/models/index.js

// I blatantly ripped this from both the following:
// https://github.com/feathersjs/generator-feathers/issues/94#issuecomment-204165134
// https://github.com/feathersjs/feathers-demos/blob/master/examples/migrations/sequelize/src/models/index.js

const Sequelize = require('sequelize');
const _ = require('lodash');

// Import the models
const account = require('./account');
const user = require('./user');

module.exports = function () {
  const app = this;

  // Note: 'postgres' is found in config/default.json as the db url
  const sequelize = new Sequelize(app.get('postgres'), {
    dialect: app.get('db_dialect'),
    logging: console.log
  });
  app.set('sequelize', sequelize);

  // Configure the models
  app.configure(account);
  app.configure(user);

  app.set('models', sequelize.models);

  // Set associations
  Object.keys(sequelize.models).forEach(modelName => {
    if ('associate' in sequelize.models[modelName]) {
      sequelize.models[modelName].associate();
    }
  });

  sequelize.sync();

  // Extra credit: Check to make sure the two instances of sequelize.models are the same...
  // Outputs: sequelize.models after sync === app.get("models")
  // I've also run this comparison on sequelize and app.get('sequelize'); _.eq() said they also were identical
  if (_.eq(sequelize.models, app.get('models')))
    console.log('sequelize.models after sync === app.get("models")');
  else
    console.log('sequelize.models after sync !== app.get("models")');
};

齐心协力

src/app.js

为了简洁起见,我从中删除了很多内容,我将模型加载到 app 中,如下所示:

const models = require('./models')
app.use(compress())
  // Lots of other statements
  .configure(models);

测试

我一直在尝试制作一个命令行实用程序来更改密码、修改用户权限和其他实用程序任务,所以我使用了 Vorpal(同样,只有 2 个链接,所以你必须自己查找如果你不熟悉——抱歉)。以下是我的 Vorpal 程序的相关片段:

cli.js

const vorpal = require('vorpal')();
const _ = require('lodash');

// Initialize app
// This seems a bit overkill since we don't need the server bit for this, but...
const app = require('./src/app');
const models = app.get('models');

// Get the models for easy access...
const User = models['user'];
const Account = models['account'];

// Run by issuing the command: node cli test
// Outputs to terminal
vorpal.command('test', 'A playground for testing the Vorpal environment.')
  .action(function(args, callback) {
    // User.belongsTo(Account); // <-- uncomment this and it works
    User.findOne({ include: [{ model: Account }]}).then((user) => {
      console.log("user.account.name:", user.account.name);
    });
  });

vorpal.show().parse(process.argv);

问题

抱歉这么久才到这里,但我不知道这里面的哪一部分是相关的,所以我只好吐了。

运行 node cli test 给我一个错误

Just to be sure...From the user model, models["account"]: account
sequelize.models after sync === app.get("models")
connect: 
Unhandled rejection Error: account is not associated to user!
    at validateIncludedElement (/vagrant/node_modules/sequelize/lib/model.js:550:11)
    at /vagrant/node_modules/sequelize/lib/model.js:432:29
    at Array.map (native)
    at validateIncludedElements (/vagrant/node_modules/sequelize/lib/model.js:428:37)
    at .<anonymous> (/vagrant/node_modules/sequelize/lib/model.js:1364:32)
    at tryCatcher (/vagrant/node_modules/bluebird/js/release/util.js:16:23)
    at Promise._settlePromiseFromHandler (/vagrant/node_modules/bluebird/js/release/promise.js:504:31)
    at Promise._settlePromise (/vagrant/node_modules/bluebird/js/release/promise.js:561:18)
    at Promise._settlePromise0 (/vagrant/node_modules/bluebird/js/release/promise.js:606:10)
    at Promise._settlePromises (/vagrant/node_modules/bluebird/js/release/promise.js:685:18)
    at Async._drainQueue (/vagrant/node_modules/bluebird/js/release/async.js:138:16)
    at Async._drainQueues (/vagrant/node_modules/bluebird/js/release/async.js:148:10)
    at Immediate.Async.drainQueues (/vagrant/node_modules/bluebird/js/release/async.js:17:14)
    at runCallback (timers.js:574:20)
    at tryOnImmediate (timers.js:554:5)
    at processImmediate [as _immediateCallback] (timers.js:533:5)

啊!

但是,如果我取消注释 User.findOne() 正上方的行,它会像一个魅力。

为什么我必须在查询关系之前立即明确设置关系?为什么在用户模型的 associate() 方法中建立的关系(大概)没有坚持?据我所知,它正在被调用——而且是在正确的模型上。它是否以某种方式被覆盖? app 是否出于某种奇怪的原因,在建立关联时在用户模型中与在 cli.js 中不同?

我真的很困惑。非常感谢你们能提供的任何帮助。

最佳答案

我不知道为什么会这样,但我确实通过进行以下更改使其正常工作。

src/models/index.js

我在导出函数末尾附近注释掉了以下 block :

Object.keys(sequelize.models).forEach(modelName => {
  if ('associate' in sequelize.models[modelName]) {
    sequelize.models[modelName].associate();
  }
});

然后我将它移动到 src/relate-models.js 中:

src/relate-models.js

/**
 * This is workaround for relating models.
 * I don't know why it works, but it does.
 *
 * @param app  The initialized app
 */
module.exports = function(app) {
  const sequelize = app.get('sequelize');

  // Copied this from src/models/index.js
  Object.keys(sequelize.models).forEach(modelName => {
    if ('associate' in sequelize.models[modelName]) {
      sequelize.models[modelName].associate();
    }
  });
}

src/app.js 中,我调用了那个函数,然后......很快就成功了。

src/app.js`

const models = require('./models')
app.use(compress())
  // Lots of other statements
  .configure(models);

require('./relate-models')(app);

结束。如果有人能解释为什么以后做完全相同的事情会奏效,请告诉我,但现在……它奏效了。

关于node.js - Sequelize 和羽毛 : When Relationships Fall Apart,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39151050/

相关文章:

javascript - 通过 Node Express 将对象作为字符串而不是 JSON 发送

mysql - 在 node.js 上使用 coalesce 和 sequelize

node.js - 如何配置sequelize以在一对多关联中返回嵌入数组?

javascript - 为什么Javascript中函数调用后面有一个点?

node.js - 为应用程序制作桌面安装程序?

javascript - 错误无法使用node.js和mysql读取未定义的属性

postgresql - 如何在 PostgreSQL 8.4 中将列数据类型从字符更改为数字

ruby-on-rails - 如何在 Rails 4 中通过 hstore 属性对结果进行排序?

node.js - 在 Sequelize 中分配外键的默认值后自动同步出错

node.js - Node js ORM 支持架构更改