At the B'more on Rails meetup tonight, Nick Gauthier, my Recipes with Backbone co-author, included an interesting discussion on frameworks vs. libraries. He pointed out that frameworks generally call user written application code whereas libraries encourage application code to call library code (usually through inheritance). It occurs to me that this is the crux of my recent struggles with my Dart MVC framework—I am having a hell of a time injecting a
sync()
method for my model and collection classes to call.I had planned on giving this a miss, but, partly egged on by Nick and partly by comments from Justin Fagnani in last night's post, I am going to try again.
My first attempt tonight focuses on using a global variable to hold this information. I do not expect to stick with a global, but just hope to use it as a proof of concept. First, I establish a "internal" version of a variable that will point to my sync function:
var internal_sync = null;Then I define an externally facing setter function for the internal version:
var internal_sync = null; set sync(fn) { internal_sync = fn; }In my framework,
internal_sync
would have a default Ajax implementation, but the developer could inject different behavior by setting a different function via the external sync=
setter.And this works, to a certain extent. In my
main()
entry point, I can set sync and invoke the new behavior:var internal_sync = null; set sync(fn) { internal_sync = fn; } main() { sync = (arg) { print("[new_sync] $arg"); return "hip new behavior"; }; print("sync: ${internal_sync('Injected, yo')}"); }(try.dartlang.org)
Inside
main()
I set the sync=
setter to a function that prints out an argument and returns a "hip new behavior". I then invoke the function that is now referred to by the internal_sync
global variable. And it works. I see the set function being called and I see the "hip new behavior":[new_sync] Injected, yo sync: hip new behaviorThe problem is that this
internal_sync
variable is not available inside libraries. For instance, the Collections.Comics.dart
library is imported and used in main()
after the updated internal_sync
is set:#import('Collections.Comics.dart', prefix: 'Collections'); // ... var internal_sync = null; set sync(fn) { internal_sync = fn; } main() { sync = (arg) { print("[new_sync] $arg"); return "hip new behavior"; }; print("sync: ${internal_sync('Injected, yo')}"); var my_comics_collection = new Collections.Comics(); my_comics_collection.fetch(); // ... }In turn, the Comics class imports and extends HipsterCollection from my framework:
#import('HipsterCollection.dart'); class Comics extends HipsterCollection { // ... }And, in
HipsterCollection
, I try to access the global internal_sync
variable:class HipsterCollection implements CollectionUnfortunately, I am denied:{ // ... fetch() { print("[fetch] sync: ${internal_sync}"); // ... } }
Exception: NoSuchMethodException - receiver: 'Instance of 'Comics'' function name: 'get:internal_sync' arguments: []] Stack Trace: 0. Function: 'Object.noSuchMethod' url: 'bootstrap' line:665 col:3 1. Function: 'HipsterCollection.fetch' url: 'http://localhost:3000/scripts/HipsterCollection.dart' line:54 col:41 2. Function: '::main' url: 'http://localhost:3000/scripts/main.dart' line:23 col:29I get similar results if I try to treat this
internal_sync
as a function.It is not possible to declare variables before
#import()
statements in Dart, nor is it possible to include code via #source()
before #import()
. So, if the problem is that the global is declared too late, then I am out of luck.I switch tactics at this point. If a global variable will not work, perhaps a class methods will. So I define a
HipsterSync
class in HipsterSync.dart
with a familiar class variable and class setter:#library('Sync layer for the Hipster MVC'); class HipsterSync { static var internal_sync; static set sync(fn) { internal_sync = fn; } }I can then import and set
HipsterSync.sync=
in main()
:// ... #import('HipsterSync.dart'); main() { HipsterSync.sync = (arg) { print("[new_sync] $arg"); return "hip new behavior"; }; print("sync: ${HipsterSync.internal_sync('Injected, yo')}"); // ... }Finally, I import and attempt to use
HipsterSync
in my collection base class:// ... #import('HipsterSync.dart'); class HipsterCollection implements CollectionAnd... that does the trick! In the Dart console, I see the output from{ // ... fetch() { print("[fetch] sync: ${HipsterSync.internal_sync('fetch, yo')}"); // ... } // ... }
main()
:[new_sync] Injected, yo sync: hip new behaviorFollowed immediately by the output from the newly set
HipsterSync.sync
when invoked by the collection's fetch()
method:[new_sync] fetch, yo [fetch] sync: hip new behaviorIt took me a while to figure that out (and much thanks to Justin Fagnani for pointing me in the right direction), but I am finally able to inject model and collection syncing behavior into my MVC framework. Using static methods to set and hold this behavior also ensures that the behavior can be shared between both the collection and model classes. I am excited.
Day #296
No comments:
Post a Comment