使用EmberData在本地存储中缓存远程数据

时间:2013-02-11 14:47:03

标签: ember.js ember-data

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

Ember Data有一个内置的存储适配器,用于通过REST API保持模型,并且还有an adapter for Local Storage(如下面的Ken所指出的)。问题(如果它是一个问题)是模型只有一个存储适配器,似乎没有任何缓存提取模型的概念,除了将它们保存在内存中。

我在此Ember wishlist和此talk by Tom Dale的评论中发现了类似的请求,但我没有发现任何迹象表明这将是Ember中的现有功能。

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

  1. 今天最好的方法是在本地存储中实施缓存模型,并根据需要将它们与远程数据同步?
  2. 这是一个计划包含在Ember中的功能,还是至少应该最终添加维护者认为的内容?
  3. 说到1),我可以想到几个策略:

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

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

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

    RemoteTodo –*sync*– Todo
                         |
                         UI
    

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

    已更新:已找到this similar question。它有一个很好的答案,但它是理论上的。我认为我需要的是一些动手技巧或示例实现的指针。

3 个答案:

答案 0 :(得分:5)

只是为了“碰撞”这个帖子一点点,因为当我研究其余的api等的ember本地缓存的解决方案时,这是最好的结果之一:

Dan Gebhardt似乎在使用Orbit.js并将其整合到Ember中做得非常出色: https://github.com/orbitjs/ember-orbit

  

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

     

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

     

Orbit.js功能:

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

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

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

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

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

  •   
  • 允许同步和异步请求。

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

  •   
  • 使用纯JavaScript对象。

  •   

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

更新:我在Orbit页面中添加了一些更具描述性的信息,因为我的帖子因为“只是”引用外部资源并且本身不包含实际解决方案而被低估。但Orbit似乎对我而言像解决方案一样,这里“包含”的唯一方法是通过链接。)

答案 1 :(得分:3)

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

答案 2 :(得分: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
};