I now have my Dart-based Hipster MVC framework able to query and update both local storage and remote data stores (via Ajax). But to do so, I have two different branches of my framework. Backbone.js has the nifty little Backbone.sync method, through which all queries and updates run. I hope to be able to replicate that in Dart.
I suspect that I will eventually succumb to typing various parts of this, but, for now, I will stick closer to Backbone's Javascript than idiomatic Dart. But immediately, I can think of a problem with this approach--it is not possible to redefine methods in Dart. By default,
Backbone.sync()
performs CRUD over Ajax. To change this behavior, one redefines Backbone.sync
(i.e. not overriding it in a sub-class). Complicating matters is that both the model and collection classes need to have access to this overridden sync()
method. This seems difficult, at best, if I cannot rewrite a single method.My first attempt is to define a function that can be
source
'd into both the model and collection. For example, it would be nice if I could simply define a sync method:// sync.dart sync() { print("[sync]"); }And then
source()
it in my collection sub-class:#library('Collection class to describe my comic book collection'); #import('HipsterCollection.dart'); #import('Models.ComicBook.dart'); #source('sync.dart'); class Comics extends HipsterCollection { get url() => '/comics'; modelMaker(attrs) => new ComicBook(attrs); }Back in my base class, I then try to replace my local storage version of
fetch()
with an attempt to invoke this sync()
function:class HipsterCollection implements Collection<HipsterModel> { // ... fetch() { try { sync(); } catch (NoSuchMethodException e) { print("*** time to define a default sync"); } } // ... }Unfortunately, this will not work—the
NoSuchMethodException
clause is always executed. It actually works as expected if I source()
the sync.dart
code directly into the HipsterCollection
base class. This is because the source
'd code is available to the enclosing library, but nowhere else (not even the base class of the enclosing library). I need some way for the concrete class to communicate the strategy for syncing back in a way that the base class can manipulate it. So I am stuck defining a "syncer" (that really does not look right) class:
#library('Sync layer for the Hipster MVC'); class HipsterSync { sync() { print("[sync]"); } }I can then specify this in my
Comics
concrete collection class:class Comics extends HipsterCollection { get url() => '/comics'; modelMaker(attrs) => new ComicBook(attrs); get syncer() => new HipsterSync(); }And use it in the base class:
fetch() {
if (syncer != null) {
syncer.sync();
}
else {
// be ajax- like
}
}
That seems to work just fine. I do not see any other options besides supplying a syncing object. It is not as nice as Backbone.sync
, with which I only need to define the sync strategy once. In my Dart MVC, I will have to supply the syncing strategy for each model and class in the application. That is less than ideal, but hopefully I can find ways to mitigate the pain.Starting tomorrow.
Day #295
What you would like to do is instead of defining sync as a top-level function, define it as a top-level variable with a function value. Then you could change the value of the variable.
ReplyDeleteThe only problem is that a top-level variable can only be initialized with a constant expression and functions aren't constant for some reason. The constant initializer rule might be relaxed, and maybe functions will be considered constant as well.
So you can have the variable be null, and require an initialization function be called to set it to it's default value.
Unless I am missing something, I cannot declare a top-level constant null and then set it later. Since it's a constant, it cannot be re-assigned, right?
DeleteI thought about doing something similar with a Map. Currently in Dartium I cannot even get map literals defined as top-level constants (`sync = {};`). Even if I could, I have not figured out a way to see that top-level constant in a library, which is what needs to see it. Libraries seem to get their own variable space that does not include globals.
Top-level variables don't need to be constant. If they're not marked final, then they are reassignable like any other variable. The confusion might come from the fact that top-level and class member _initializer_ expressions must be constant expressions - that is, they have to yield a deeply immutable object. But that's just the RHS of the assignment, after the program starts running the variables and fields can be reassigned.
DeleteThe constant expression requirement is so that the VM can load the program right up to the point where main() is ready to run, and then take a snapshot of memory and cache it. Any time the program is re-run, it can start from the snapshot.
So in your case you could have a top-level var, static var, or class member initialized to null and then set in some initialize() function. Or, probably better, you can have a function that returns the sync function, and a setter as well. Then you don't need the explicit initialization step.
Ah, thanks that helps.
DeleteI still don't think that the top-level var is going to work, though. If I declare `var sync = null` outside of main() in main.dart, that variable is not accessible from inside subsequent libraries that are pulled in via `#import()`.
It might work inside a class -- as a static method or singleton. I'll play with that.
Yup, class variables / setters did the trick (could get top-level variables working): http://japhr.blogspot.com/2012/02/injecting-shared-behavior-in-dart.html
Delete*Huge* thanks for your help!