Milkcocoaのデータストアから重複しているデータを削除する

Milkcocoaのデータストアから重複しているデータを削除する

久しぶりにMilkcocoaを使ったアプリについてです。

ほんとにWebデザイナーでもできる?Milkcocoaを使ったバックエンド楽ちん実装(3)で、自作の麻雀点数計算アプリsuzumeで計算した結果をMilkcocoaのデータストアに保存していました。

このアプリは、計算結果画面で戻るボタンを押すと、入力値を保ったまま入力画面に戻れます。そこでブラウザで「進む」ではなく、「計算する」ボタンを再度押すと、まったく同じ内容の計算結果がMilkcocoaに送信されてしまいます。

別に使うときに困るわけではないのですが、同じデータを複数持つのも無駄だし、できれば重複を除外したいです。

考えられる方法はいくつかありますね。

  1. 計算結果を記憶させておき、連続して計算した場合は送信しない
  2. 同一の計算結果がデータストアにある場合、送信しない
  3. とりあえず送信させておいて、データストアから重複を探して削除する

などなど。

まず、一番目。
計算結果を記憶させておき、連続して計算した場合は送信しない。
アプリを使用中の実際のユーザーの操作から考えると、これでだいたい防げます。
クライアント側のjsだけで完結するので楽そうです。
でも、間を空けて同じあがり手を入力したら結局重複したデータが送信されてしまいますし、いつかどこかで、他のユーザーが同じ手を入力することがあるかもしれません。

次に、二番目。
同一の計算結果がデータストアにある場合、送信しない。
データストアのデータをあらかじめ取得しておいて、その中に重複するデータがあれば送信処理を行わないという方法。
ほんとにWebデザイナーでもできる?Milkcocoaを使ったバックエンド楽ちん実装(4)でstream()を使ってデータを取得する処理をやったので、これを再利用すれば簡単なのですが・・・

重複が起こるのは、ほとんどの場合ユーザーの連続した操作によって送信された、という状況です。
つまりそれを防ぐには、一度データを送信したあと、次にユーザーが送信する時点で、前回送信されたデータが含まれている配列をまたstream()でとってこないといけません。
全件取得が終わるまではデータの探索・比較ができません。
非同期で取得してくることになるので、そのへんの対策が必要で面倒です。
しかもその間、ユーザーの操作を止めることになってしまい、使い勝手が悪くなりますね(;´Д`)

データの送信自体は、点数計算をするだけなら不要です。
別のアプリでデータを使いたいためにやってるだけなので、そのために計算ができなくなるのは、計算アプリとしては本末転倒です。

結局、三番目を選ぶことにしました。
とりあえず送信させておいて、データストアから重複を探して削除する。

送信されてから手動で削除するまで、重複データが存在してMilkcocoaのデータ数を圧迫することで困るのは私だけです。
ていうか最初に言ったように、さほど困っていません。
さほど困っていないのだからアプリ内でがんばらずに、気が向いたときに削除できればいいです。

そこで、重複データを削除するための簡単なツールをつくりました。

単純にローカルに置いたこのHTMLをブラウザで開いて、実行するだけです。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>milkcocoa重複データ削除</title>
<script src="https://cdn.mlkcca.com/v2.0.0/milkcocoa.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script>
var i = 0;
var len = 0;
var data = [];
var haishi = [];
var milkcocoa = new MilkCocoa('アプリID.mlkcca.com');
var fungo = milkcocoa.dataStore('fungo');

$(function(){

  $(window).on('load',function(){
    fungo.stream().size(999).sort('asc').next(function(err, dataList) {
      data = dataList;
    });
  });

  $('button').on('click',function(){
    len = data.length;
    for (i=0;i<len;i++){
      haishi.push(JSON.stringify(data[i].value));
    }
    len = data.length - 1;
    for (i=len;i>0;i--){
      if(haishi.indexOf(JSON.stringify(data[i].value))!==i){
        fungo.remove(data[i].id, function(err, removed){
          console.log(data[i].id+"を削除しました");
        }, function(err) {
          console.log(err);
        });
      }
    }
    console.log('完了しました');
  });
  
});

</script>
<style>
button {
  appearance: none;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: block;
  width: 10em;
  height: 3em;
  line-height: 3em;
  margin: auto;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1.2rem;
}
</style>
</head>

<body>
<button>重複削除実行</button>
</body>
</html>

ひとつ注意点としては、ローカルからこういうのを実行しようとすると、ChromeではXHRエラーになります。
同じようなことを、React使ってSPAを作るよ(6)でも説明しています。
てことで私はこのファイルをFirefoxで開いて実行させています。

どんな処理をしているのかざっくり説明します。

var milkcocoa = new MilkCocoa('アプリID.mlkcca.com');
var fungo = milkcocoa.dataStore('fungo');

$(function(){

  $(window).on('load',function(){
    fungo.stream().size(999).sort('asc').next(function(err, dataList) {
      data = dataList;
    });
  });

このあたりは以前の記事のままですね。
Milkcocoaオブジェクトを用意して、データリストをstream()でとってきます。
size()指定をしないとデフォルト50件なので999としておきました。
(データストアのデータが999件より少なくても特に問題はありません)
1000件以上取得するときのやり方はMilkcocoaのドキュメントにあるので参照してください。

とりあえずファイルを開いたら自動でデータをとってくるようにしてあります。
次に、ボタンをクリックしたらいよいよ比較します。
あまりにもすばやくボタンを押したら、stream()が終わってない可能性もあります。
まあこのへんは手運用なのでよきようにしてください。

  $('button').on('click',function(){
    len = data.length;
    for (i=0;i<len;i++){
      haishi.push(JSON.stringify(data[i].value));
    }

とってきたdataのvalueだけの配列をつくります。
なんでわざわざこんなことしてるのかというと、このあとでdata[i].valueをそのまま比較しようとしても、ありがたいことにちゃんとdata[i]のオブジェクトだけを見つけてくれるので、valueの中身がまったく同じでも重複しているほかのデータを見つけられないからです(´ε`;)ウーン…

まあなんかもっとスマートな方法ありそうですがわからないのでJSON.stringifyを使って文字列にしちゃいます。

    len = data.length - 1;
    for (i=len;i>0;i--){
      if(haishi.indexOf(JSON.stringify(data[i].value))!==i){
        fungo.remove(data[i].id, function(err, removed){
          console.log(data[i].id+"を削除しました");
        }, function(err) {
          console.log(err);
        });
      }
    }

そしたら今度こそ、とってきたデータを1件ずつ見ていきます。
さっき文字列にして配列haishiに入れたvalueたちの中から、data[i].valueの文字列と同じものを探します。

普通にindexOf()しちゃうと、当たり前ですがdata[i]自身の位置が見つかってしまいます。
そこで、for()を逆側から回すようにしました。

配列は[0]から始まるのでデータの個数から-1した数をiに代入。

len = data.length - 1

そこから1ずつ引いていきます。

for (var i=len;i>0;i--){

もしもhaishiの中で同じ文字列があって、そのインデックスが今みているdata[i]と違ったら、重複ですね!

      if(haishi.indexOf(JSON.stringify(data[i].value))!==i){

重複があることがわかったら、今みているdata[i]のidをMilkcocoaに渡してremove()します。

もし同じデータが3個以上あっても、後ろから前に向かっていくにつれて同じように比較して後ろにあるdata[i]が消えていくので、インデックスがずれることはありません。

どなたか、他にいい方法あったら教えてください(人∀・)