Sails.jsを試していて、サードパーティのAPIからデータをインポートし、MySQLテーブルに保存するアプリを書いています。基本的に、データをアプリに同期してさらに分析したり、レコードを更新したり、必要に応じて新しいレコードを作成したりしようとしています。
SailsのAPIを調べたところ、レコードを検索、作成、更新するメソッドが見つかりましたが、状況に基づいてレコードを挿入/更新する組み込みのメソッドがありません。私は何かを見落としましたか、それとも自分でこれを実装する必要がありますか?
これを自分で実装する必要がある場合、挿入/更新の優れたデザインパターンを知っている人はいますか?
これは私がそれがどのように見えるかと思うものです…
_.each(importedRecords, function(record){
MyModel.find({id: record.id}).exec(function findCB(err, found){
if(found.length){
MyModel.update(record.id, task).exec(function(err, updated){
if(err) { //returns if an error has occured, ie id doesn't exist.
console.log(err);
} else {
console.log('Updated MyModel record '+updated[0].name);
}
});
}else{
MyModel.create(record).exec(function(err, created){
if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
console.log(err);
} else {
console.log('Created client record '+created.name);
}
});
}
});
});
私は正しい方向に向かっていますか、それとももっとエレガントな解決策がありますか?
また、このアプリではさまざまなモデルを扱っています。つまり、各モデルでこのコードブロックを再作成する必要があります。ベースモデルオブジェクトを拡張して、すべてのモデルにこの機能を追加する方法はありますか?.
ありがとう、ジョン
クリティカルマッシュコードを書き直したので、コードが少なくなり、より一般的になりました。これで、findOrCreateを呼び出すのと同じ方法でupdateOrCreateを呼び出すことができます。そしてそれはそのように見えます:
module.exports.models = {
updateOrCreate: function(criteria, values){
var self = this; // reference for use by callbacks
// If no values were specified, use criteria
if (!values) values = criteria.where ? criteria.where : criteria;
return this.findOne(criteria).then(function (result){
if(result){
return self.update(criteria, values);
}else{
return self.create(values);
}
});
}
};
そうすれば、同じ方法で基準を書くことができます。キーを操作する必要はなく、コードは非常に単純です。
Sails0.10にはfindOrCreate(criteria, attributes, callback)
があります。 SailsDocs を参照してください。
criteria
は、「find」ビットの検索条件です(find()と同じ構文)。
attributes
は、「create」ビットで見つからない場合に使用されるデータです(create()と同じ構文)。
次に例を示します。
MyModel.findOrCreate({name:'Walter'},{name:'Jessie'}, function (err, record){ console.log('What\'s cookin\' '+record.name+'?');
また、 Waterlineリポジトリ (例については テスト を参照)および Waterlineドキュメント に記載されている他の複合クエリメソッドがあることにも注意してください。
次の各基本メソッドは、デフォルトでコレクションインスタンスで使用できます。
- findOne
- 見つける
- 作成する
- 更新
- 破壊
- カウント
さらに、次のヘルパーメソッドもあります。
- createEach
- findOrCreateEach* <-必要なもののように見えます(ヒントは基準/属性の配列を使用します)*
- findOrCreate
- findOneLike
- findLike
- startWith
- extendsWith
- 含まれています
コレクション属性に基づいて、動的ファインダーもあります。したがって、name属性を指定すると、次のクエリを使用できます。
- findOneByName
- findOneByNameIn
- findOneByNameLike
- findByName
- findByNameIn
- findByNameLike
- countByName
- countByNameIn
- countByNameLike
- nameStartsWith
- nameEndsWith
- nameContains
何かを見落とすということに関しては、それはそこにありますが、それはまだメインのSailsドキュメントにないので、答えは「はい」と「いいえ」です。
あなたの解決策は正しいです。ウォーターライン(sails.jsのORM)でこれを行う他の方法はありません。しかし、いくつかのデータベースにはこの場合の機能があります。
MySQL
REPLACE INTO table SET id = 42, foo = 'bar';
(主キーまたは一意キーを使用。auto_incrementを使用する場合はかなりくだらない;-)
Waterlineでは、Model。query()-Functionを使用して直接SQLを実行できます(参照: http://sailsjs.org/ #/ documentation/reference/waterline/models/query.html )
MongoDB
db.collection.update(
<query>,
<update>,
{ upsert: true }
)
アップサートフラグの意味:クエリで何も見つからなかったために更新できない場合は、この要素を作成してください!
Waterlineでは、Model。native()-Functionを使用して直接mongoDB-Commandsを実行できます(参照: http:// sailsjs。 org /#/ documentation/reference/waterline/models/native.html )
結論
あなたは速い実行を必要とします(そしてあなたが多くの要求を持っているならそれ以上のことです)私はnative/sql-functionsを使うことを提案します。しかし、一般的に、私はORMシステムの柔軟性が大好きで、データベース固有の関数を使用するたびに処理が難しくなります。
User3351722に感謝します、私もORMシステムを使用することを好みます。上記のソリューションを一般的なモデルメソッドとして実装してみました。 ( Sails.jsモデルの属性とライフサイクル関数を継承する に基づく)。
Config/models.jsを編集し、インデックス列の名前、挿入または更新するデータ、およびコールバック関数を受け取る新しい関数insertOrUpdate
を追加しました。
module.exports.models = {
insertOrUpdate: function(key, record, CB){
var self = this; // reference for use by callbacks
var where = {};
where[key] = record[key]; // keys differ by model
this.find(where).exec(function findCB(err, found){
if(err){
CB(err, false);
}
// did we find an existing record?
if(found && found.length){
self.update(record[key], record).exec(function(err, updated){
if(err) { //returns if an error has occured, ie id doesn't exist.
CB(err, false);
} else {
CB(false, found[0]);
}
});
}else{
self.create(record).exec(function(err, created){
if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
CB(err, false);
} else {
CB(false, created);
}
});
}
});
}
};
これは、インデックスを持つテーブル/コレクションでのみ機能します。喫水線のモデルからキー名を内省する方法がわからないので、フィールド名に文字列として渡します。
コントローラ内でメソッドを使用する方法は次のとおりです…
_.each(clients, function(client){
Client.insertOrUpdate('client_id', client, function(err, updated){
if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
sails.log(err);
} else {
sails.log('insertOrUpdate client record ', updated.organization); //+updated[0].name
}
});
});
私はこの方法を3つの異なるモデルで試しましたが、これまでのところ、とても良いです。それらはすべてMySQLテーブルであり、モデルにはすべて定義済みのインデックスがあります。別のデータストアを使用している場合は、マイレージが非常に高くなる可能性があります。
誰かがこれを改善する方法を見つけたら、私たちに知らせてください。
これが私のやり方です。config/ models.jsを拡張して機能を含め、アダプターに正しいメソッドがあるかどうかを確認します。あなたはそれを約束としてまたは通常と呼ぶことができます。
var normalize = require('sails/node_modules/waterline/lib/waterline/utils/normalize');
var hasOwnProperty = require('sails/node_modules/waterline/lib/waterline/utils/helpers').object.hasOwnProperty;
var defer = require('sails/node_modules/waterline/lib/waterline/utils/defer');
var noop = function() {};
module.exports.models = {
/**
* [updateOrCreate description]
* @param {[type]} criteria [description]
* @param {[type]} values [description]
* @param {Function} cb [description]
* @return {[type]} [description]
*/
updateOrCreate: function (criteria, values, cb) {
var self = this;
var deferred;
// Normalize Arguments
if(typeof cb !== 'function') {
deferred = defer();
}
cb = cb || noop;
criteria = normalize.criteria(criteria);
if (criteria === false) {
if(deferred) {
deferred.resolve(null);
}
return cb(null, []);
}
else if(!criteria) {
if(deferred) {
deferred.reject(new Error('No criteria or id specified!'));
}
return cb(new Error('No criteria or id specified!'));
}
// Build Default Error Message
var errFind = 'No find() method defined in adapter!';
var errUpdate = 'No update() method defined in adapter!';
var errCreate = 'No create() method defined in adapter!';
// Find the connection to run this on
if(!hasOwnProperty(self.adapter.dictionary, 'find')){
if(deferred) {
deferred.reject(errFind);
}
return cb(new Error(errFind));
}
if(!hasOwnProperty(self.adapter.dictionary, 'update')){
if(deferred) {
deferred.reject(errUpdate);
}
return cb(new Error(errUpdate));
}
if(!hasOwnProperty(self.adapter.dictionary, 'create')) {
if(deferred) {
deferred.reject(errCreate);
}
return cb(new Error(errCreate));
}
var connNameFind = self.adapter.dictionary.find;
var adapterFind = self.adapter.connections[connNameFind]._adapter;
var connNameUpdate = self.adapter.dictionary.update;
var adapterUpdate = self.adapter.connections[connNameUpdate]._adapter;
var connNameCreate = self.adapter.dictionary.create;
var adapterCreate = self.adapter.connections[connNameCreate]._adapter;
adapterFind.find(connNameFind, self.adapter.collection, criteria, normalize.callback(function before (err, results){
if (err) {
if(deferred) {
deferred.reject(err);
}
return cb(err);
}
if(results && results.length > 0){
adapterUpdate.update(connNameUpdate, self.adapter.collection, criteria, values, normalize.callback(function afterwards (err, updatedRecords) {
if (err) {
if(deferred) {
deferred.reject(err);
}
return cb(err);
}
deferred.resolve(updatedRecords[0]);
return cb(null, updatedRecords[0]);
}));
}else{
adapterCreate.create(connNameCreate, self.adapter.collection, values, normalize.callback(function afterwards (err, createdRecord) {
if (err) {
if(deferred) {
deferred.reject(err);
}
return cb(err);
}
deferred.resolve(createdRecord);
return cb(null, createdRecord);
}));
}
}));
if(deferred) {
return deferred.promise;
}
}
}