Wednesday, June 5, 2013

Dart localStorage Migrations

‹prev | My Chain | next›

I have two more issues that need to be resolved before putting the Dart version of the ICE Code Editor in Beta. The problem that presents more of a challenge involves working with the existing localStorage on http://gamingjs.com/ice/. Even though the beta version will reside at http://gamingjs.com/ice-beta/, the two will share localStorage.

The problem with sharing localStorage is that the JSON data is slightly different between the old JavaScript version and the new Dart version. The old format looks something like the following:
[
 {
   "filetype":"text/plain",
   "autoupdate":true,
   "code":"...",
   "filename":"moving_players/code"
 },
 {
   "filetype":"text/plain",
   "autoupdate":true,
   "code":"...",
   "filename":"moving_players/code.b2"
 }
 // ...
]
I think the "filetype" and "autoupdate" attributes can be ignored. Even in the JavaScript version, filetype never changes. I think the "autoupdate" feature is more appropriate at the editor level rather than the project level. In other words, neither attribute matters to either the existing or new version of ICE. The "code" attribute is identical between the two versions.

The problem is the "filename" attribute. In the Dart version, I named that attribute "title" to reflect meaning within the data store. Unfortunately, "title" / "filename" serves as the unique identifier for a project, so I need to be very cautious with it. And, since the two versions need to co-exist at least for a little while, I have to come up with a solution that won't break either.

My first instinct is to punt the problem until the future. I could change the attribute name to "filename" in the new Dart version and it ought to work fine. Then, at some later date, I could write an in-browser "data migration" to convert the localStorage format.

Another option is for the Dart version to work with either format. That is, it would fallback to lookup with "filename" if that attribute is not present and it would write both a "title" and "filename" attribute for new and updated records.

I think that I will go for option #1. I am not particularly fond of the legacy attribute name, but it seems the safer of the two options.

So I add a title constant to the Store class and use it wherever I had been using 'title':
class Store implements HashMap<String, HashMap> {
  /// The record ID attribute
  const String title = 'filename';

  HashMap get currentProject {
    if (this.isEmpty)
      return {title: 'Untitled', 'code': ''};

    return projects.first;
  }

  String get currentProjectTitle => currentProject[title];
  // ...
}
That ought to do the trick. But what about the data that I have locally? It's not much, so I suppose that I could lose it. But if it has that low a value, perhaps it is a good way to try out data migrations.

So I add a migration method to the constructor:
class Store implements HashMap<String, HashMap> {
  // ...
  Store() {
    _migrateFromTitleIdToFilename();
  }

  _migrateFromTitleIdToFilename() {
    if (currentProject.containsKey(title)) return;
    _projects = projects.map((p) {
        p[title] = p['title'];
        return p;
      }).
      toList();
    _sync();
  }
  // ...
}
If the data has already been migrated then _migrateFromTitleIdToFilename() returns immediately without making any changes. But, if the first project does not have the new title attribute, then I re-map the entire project list, assigning the title value to the old 'title' value. Lastly, I _sync the data back into localStorage so that the new format will persist

And that actually does the trick. I will likely comment out that migration so that it never affects real users—it seems safe, but why risk it? But I can leave it for other ICE developers to run if they like and it can serve as a template for getting started in the distant future when I finally do migrate the attribute from "filename" to "title".


Day #773

No comments:

Post a Comment