Sequelize和Feathers:当关系分崩离析时

时间:2016-08-25 17:07:21

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

经过两天的努力弄清楚为什么我的Sequelize模特没有致力于他们的关系,我已经决定是时候向所有人寻求建议。

这是故事。

我正在使用Postgres(9.4)数据库编写Feathers JS应用程序,并将Sequelize作为驱动程序。我在Feathers Docs中完成了设置,并通过一些哄骗,让我的迁移得以运行。

根据我的理解,必须特别考虑使用Sequelize进行双向关系,因为如果ModelA引用ModelB,则必须已定义ModelB,但如果ModelB 1}}引用ModelA ......好吧,我们遇到了一个依赖循环。

这是因为文档说的依赖循环使用此处描述的方法来定义模型。" (好吧,技术上它只是假设"使用了这样的结构。而且,我只能发布2个链接,否则我会链接那个吸盘。对不起。)我发现了Feathers demo中的相同结构。

当然,我反映了所有这一切(当然,除非我错过了一个小而重要的细节),但是......仍然没有骰子。

这是我正在看的内容:

迁移

迁移/创建-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');
  }
};

迁移/创建-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 users

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

到目前为止一切顺利,对吧?

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

模型

SRC /模型/ 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 /模型/ 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 /模型/ 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进行关联时是不一样的?

我真的很困惑。所有人都可以提供任何帮助,非常感谢。

1 个答案:

答案 0 :(得分:2)

我不知道为什么会这样,但我确实通过进行以下更改来实现它。

SRC /模型/ index.js

我在导出函数末尾附近注释掉了下面的块:

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

然后我将其移至src/relate-models.js

的src /涉及-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我调用了这个函数并且... presto change-o,它起作用了。

的src / app.js`

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

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

结束。如果有人解释为什么以后做同样的事情会有效,请告诉我,但是现在......它有效。