Angular-style Dependency Injection... in Ruby?
- Tim Raymond
- February 4, 2014
- Reading time: 4 minutes.
When I first saw Angular’s automatic dependency injection, my mind was blown. It was reminiscent of the awe that I had when I first saw the magic methods ActiveRecord defines on your models in a Rails application. As I’ve been spending plenty of quality time with Angular lately, I thought it would be fun to try to bring Angular’s automatic dependency injection over to Ruby.
There is certainly no shortage of blog posts explaining how Angular’s dependency injector works, but in order to build one in Ruby, it’s vital that we understand how it works. If you’re already well versed in how Angular’s injector works, feel free to scroll ahead back to Ruby land.
Whenever you define a function in Javascript, you can ask for that function as a string:
|
|
…which initially seems like a rather useless feature. Fear not though, we have regular expressions! Let’s see if we can crack out those function arguments to do something interesting with them.
|
|
Cool! Those regexes are fairly opaque, but the basic idea is to match the full string of arguments, sans enclosing parens with a capture group. The inner group is noncapturing so I can treat the individual argument, comma, space sequences as a single character. Once we have that, we split on comma, space.
Using these strings, we can look up preregistered stuff in something
like a hash, and then func.apply()
with the proper arguments.
|
|
Great! Now how do we bring this over to Ruby?
Ruby-land
While reading the fantastic “Ruby Under a Microscope” by Pat Shaunessy, he demonstrated an amazing feature of MRI that allows you to view the YARV generated for anything you like.
|
|
Output:
== disasm: <RubyVM::InstructionSequence:foo@(irb)>======================
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] bar<Arg>
0000 trace 8 ( 1)
0002 trace 1 ( 2)
0004 putself
0005 getlocal_OP__WC__0 2
0007 opt_send_simple <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0009 trace 16 ( 3)
0011 leave ( 2)
You’ll notice that one section of the output is the “local table”. In short, this is where any local variables and arguments to our function are placed. They were even kind enough to give us the name of the arugment…
|
|
Conveniently, the argument names we’re interested in all begin with square brackets, so we target these with the regular expression. We would like to be able to call this method by just mentioning its name. A bit of aliasing fancy footwork can give us that.
|
|
Also, since the
def
keyword in Ruby 2.1 returns a symbol, defining injected methods is
as simple as:
|
|
Output:
Hi there!
much magic
We also made sure to return the method name as a symbol from injected
so as to allow chaining of def prefixes :). This is perhaps the most
understated but awesome feature of Ruby 2.1. You could easily use this
feature to also, for example, auto-wrap methods in a Mutex.synchronize
with synchronized def foo
, and all other manner of method annotation
goodness.
While I wouldn’t consider any of this production-ready by any stretch of
the imagination, it does illustrate some nifty things we can do by
prefixing def
s with other methods and regexing the generated YARV of
other methods. Go forth and experiment!