私の Sequelize モデルが関係にコミットしていない理由を理解しようと 2 日間試みた後、皆さんにアドバイスを求める時が来たと判断しました。
これが話です。
Sequelize をドライバーとして Postgres (9.4) データベースを使用して Feathers JS アプリを作成しています。Feathers Docsでセットアップを実行し、いくつかの説得力を持って、移行を実行しました。
私が理解していることから、ModelA
Sequelizeで双方向の関係を機能させるには特別な考慮が必要です。ModelB
ModelB
ModelB
ModelA
ドキュメントが「ここで説明されている方法を使用してモデルを定義する」と言っているのは、その依存関係ループのためです。(わかりました、技術的には、そのような構造が使用されていると「想定」しているだけです。また、2つのリンクしか投稿できません。それ以外の場合は、その吸盤をリンクします。申し訳ありません。)Feathers demoで同じ構造を見つけました。
当然のことながら、私はそのすべてをミラーリングしました (もちろん、小さくても重要な詳細を見逃していない限り) が、まだサイコロはありません.
これが私が見ているものです:
移行
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 users
:
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/モデル/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
か?
私は本当にかなり困惑しています。皆さんが与えることができるどんな助けも、とても感謝しています。