web-dev-qa-db-ja.com

EmberDataを使用してローカルストレージにリモートデータをキャッシュする

Emberを使用したリモートオブジェクトのロードとキャッシュについて質問があります。 Ember APIを介してサーバー側ストレージを使用するRESTアプリを開発しています。フェッチされたデータの一部はめったに変更されないため、アプリケーションをロードするたびにサーバーを使用する必要はありません。ただし、これは、オフラインで動作し、データをサーバーに保存する必要があるアプリにとっても問題です。

Ember Dataには、REST APIを介してモデルを永続化するためのストレージアダプターが組み込まれています。 ローカルストレージ用のアダプター もあります(以下のKenが指摘) )問題(問題がある場合)は、モデルにストレージアダプターが1つしかないことであり、フェッチされたモデルをメモリに保持する以外にキャッシュするという概念はないようです。

これと同様のリクエストが Emberウィッシュリスト とこれへのコメント Tom Daleによるトーク で見つかりましたが、これがEmberの既存の機能であるという兆候は見つかりませんでした。

2つの質問があります(最初の質問は重要な質問です)。

  1. キャッシュされたモデルをローカルストレージに実装し、必要に応じてリモートデータと同期するための最良の方法は何ですか?
  2. これはEmberに含まれる予定の機能ですか、それとも少なくともメンテナが最終的に追加する必要があると感じる機能ですか?

1)に関しては、いくつかの戦略を考えることができます。

a)既存のアダプターを拡張し、カスタムリモート同期メカニズムを追加します。

App.Store.registerAdapter('App.Post', DS.LSAdapter.extend({
  // do stuff when stuff happens
}));

b)個別のモデルクラス(リモートオブジェクト用に1セット、ローカルオブジェクト用に1セット)を維持し、必要に応じてそれらの間で同期します。標準のTodoケースの場合:

RemoteTodo –*sync*– Todo
                     |
                     UI

これが本当の初心者の質問であり、これには十分に確立されたパターンがあることを願っています。

更新:見つかりました この同様の質問 。良い答えがありますが、それは一種の理論的です。私が必要とするのは、いくつかの実践的なヒントや実装例へのポインタだと思います。

41
hannes_l

このスレッドを少し「バンプ」するだけです。これは、ember Restful APIのローカルキャッシュなどのソリューションを調査したときの上位の結果の1つだったためです。

Dan Gebhardtは、Orbit.jsとそのEmberへの統合で血まみれの良い仕事をしているようです: https://github.com/orbitjs/ember-orbit

Orbitは、データソースへのアクセスを調整し、コンテンツの同期を維持するためのスタンドアロンライブラリです。

Orbitは、オフライン操作、ローカルキャッシュのメンテナンスと同期、元に戻す/やり直しスタック、アドホック編集コンテキストなど、クライアント側アプリケーションで高度な機能を構築するための基盤を提供します。

Orbit.jsの機能:

  • アプリケーション内のさまざまなデータソースをいくつでもサポートし、共通のインターフェイスを介してそれらへのアクセスを提供します。

  • 優先順位やフォールバックプランを指定する機能など、さまざまなソースによる要求の実行を可能にします。

  • ソース間でレコードが異なる状態で同時に存在できるようにします。

  • ソース間で変換を調整します。可能な場合は自動的にマージを処理しますが、完全なカスタム制御が可能です。

  • ブロッキング変換と非ブロッキング変換を許可します。

  • 同期および非同期の要求を許可します。

  • 操作の逆を追跡することにより、トランザクションと元に戻す/やり直しをサポートします。

  • プレーンなJavaScriptオブジェクトを操作します。

そして、オービットについての彼の素晴らしいスピーチとスライドをお見逃しなく:
Orbit.jsの紹介

[〜#〜] update [〜#〜]:私の投稿が「ただ」に反対票を投じたので、Orbitページからさらに説明的な情報を追加しました「外部リソースを参照し、実際のソリューション自体は含まれていません。しかし、Orbitはソリューションが好きなようで、ここにこれを「含める」唯一の方法はリンクを介することです。)

役立つと思われるローカルストレージアダプタの実装があります。ご覧ください https://github.com/rpflorence/ember-localstorage-adapter

3
ken

これを行う方法は次のとおりです。アダプターのミックスイン。メソッドlocalStoreRecordを使用して、レコードをキャッシュできます。最後に、ストアをプリロードするためのイニシャライザーです。

ローカルストレージは、文字列化されたオブジェクトの単なるkey:valueストアであるため、すべてのアプリケーションデータを単一のキーで保存できます。

注:これはes6モジュールを使用しています

// app/mixins/local-storage.js

import Ember from 'ember';

export default Ember.Mixin.create({
  appName: 'myApp',
  // how many records per model to store locally, can be improved.
  // needed to prevent going over localStorage's 5mb limit
  localStorageLimit: 5,
  localStoreRecord: function(record) {
    var data = JSON.parse(localStorage.getItem(this.appName));
    data = data || {};
    data[this.modelName] = data[this.modelName] || [];
    var isNew = data[this.modelName].every(function(rec) {
      rec.id !== record.id; 
    });
    if (isNew) {
      data[this.modelName].Push(record);
      if (data[this.modelName].length > this.localStorageLimit) {
        data[this.modelName].shift();
      }
      localStorage.setItem(this.appName, JSON.stringify(data));
    }
  }
});
// app/adapters/skateboard.js

import DS from 'ember-data';
import Ember from 'ember';
import LocalStorageMixin from '../mixins/local-storage';

export default DS.RESTAdapter.extend(LocalStorageMixin, {
  modelName: 'skateboard',
  find: function(store, type, id) {
    var self = this;
    var url = [type,id].join('/');
    return new Ember.RSVP.Promise(function(resolve, reject) {
      Ember.$.ajax({
        url: 'api/' + url,
        type: 'GET'
      }).done(function (response) {
        // cache the response in localStorage
        self.localStoreRecord(response);
        resolve({ type: response });
      }).fail(function(jqHXR, responseStatus) {
        reject(new Error(type +
         ' request failed with status=' + reponseStatus);  
      });
    });
  },
  updateRecord: function(store, type, record) {
    var data = this.serialize(record, { includeId: true });
    var id = record.get('id');
    var url = [type, id].join('/');
    return new Ember.RSVP.Promise(function(resolve, reject) {
      Ember.$.ajax({
        type: 'PUT',
        url: 'api/' + url,
        dataType: 'json',
        data: data
      }).then(function(data) {
        // cache the response in localStorage
        self.localStoreRecord(response);
        resolve({ type: response });
      }).fail(function(jqXHR, responseData) {
        reject(new Error(type +
         ' request failed with status=' + reponseStatus);
      });
    });
  }
});
// app/initializers/local-storage.js

export var initialize = function(container/*, application*/) {
  var appName = 'myApp';
  var store = container.lookup('store:main');
  var data = JSON.parse(localStorage.getItem(appName));
  console.log('localStorage:',data);
  if (!data) {
    return;
  }
  var keys = Object.keys(data);
  if (keys.length) {
    keys.forEach(function(key) {
      console.log(key,data[key][0]);
      store.createRecord(key, data[key][0]);
    });
  }
};

export default {
  name: 'local-storage',
  after: 'store',
  initialize: initialize
};
1
Harrison Powers