41

我有一个关于使用 Ember 加载和缓存远程对象的问题。我正在开发一个通过 REST API 使用服务器端存储的 Ember 应用程序。某些获取的数据很少更改,因此每次应用程序加载时都从服务器获取数据是不必要的。但这对于需要离线工作并仍将数据保存到服务器的应用程序来说也是一个问题。

Ember Data 有一个内置的存储适配器,用于通过 REST API 持久化模型,还有一个用于本地存储的适配器(如下面的 Ken 指出的)。问题(如果有问题的话)是一个模型只有一个存储适配器,除了将它们保存在内存中之外,似乎没有任何缓存获取的模型的概念。

我在此Ember 愿望清单和Tom Dale对本次演讲的评论中发现了类似的请求,但我没有发现任何迹象表明这将是 Ember 中的现有功能。

我有两个问题(第一个是重要的):

  1. 今天,在本地存储中实现缓存模型并根据需要将它们与远程数据同步的最佳方式是什么?
  2. 这是计划包含在 Ember 中的功能,还是至少维护者认为最终应该添加的功能?

当谈到 1) 时,我可以想到几个策略:

a) 扩展现有适配器并添加自定义远程同步机制:

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

b) 维护单独的模型类——一组用于远程对象,一组用于本地对象——并根据需要在它们之间进行同步。使用标准 Todo 案例:

RemoteTodo –*sync*– Todo
                     |
                     UI

我有点希望这是一个真正的菜鸟问题,并且有一个很好的既定模式。

更新:发现这个类似的问题。它有一个很好的答案,但它是一种理论上的。我认为我需要的是一些实用技巧或指向示例实现的指针。

4

3 回答 3

5

只是为了稍微“提高”这个线程,因为当我研究 ember local cache of restful api 等解决方案时,它是最好的结果之一:

Dan Gebhardt 似乎在 Orbit.js 及其与 Ember 的集成方面做得非常好: https ://github.com/orbitjs/ember-orbit

Orbit 是一个独立的库,用于协调对数据源的访问并保持其内容同步

Orbit 为在客户端应用程序中构建高级功能奠定了基础,例如离线操作、本地缓存的维护和同步、撤消/重做堆栈和临时编辑上下文。

Orbit.js 功能:

  • 在应用程序中支持任意数量的不同数据源,并通过通用接口提供对它们的访问。

  • 允许满足不同来源的请求,包括指定优先级和后备计划的能力。

  • 允许记录同时存在于不同来源的不同状态。

  • 协调跨源的转换。尽可能自动处理合并,但允许完全自定义控制。

  • 允许阻塞和非阻塞转换。

  • 允许同步和异步请求。

  • 通过跟踪操作的逆向来支持事务和撤消/重做。

  • 使用纯 JavaScript 对象。

不要错过他关于 Orbit 的精彩演讲和幻灯片:
Orbit.js 简介

更新:我从 Orbit 页面添加了一些更多描述性信息,因为我的帖子因“仅”引用外部资源而不包含实际解决方案而被否决。但在我看来,Orbit 似乎是解决方案,也是“包括“这是通过链接。)

于 2014-07-07T22:58:13.800 回答
3

有一个本地存储适配器的实现,您可能会发现它很有用。看看https://github.com/rpflorence/ember-localstorage-adapter

于 2013-02-11T17:39:53.060 回答
1

这是一种方法。适配器的 mixin,具有localStoreRecord可用于缓存记录的方法,最后是用于预加载存储的初始化程序。

本地存储只是字符串化对象的键:值存储,因此我们可以将所有应用程序数据存储在一个键下。

注意:这是使用 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
};

于 2014-11-12T05:46:20.293 回答