Behold! You can now use JavaScript-like objects in Ruby!
Why though? Why do this?
Please, hold your applause and confused looks while I explain. I originally learned how to code in Ruby and it’s true that JavaScript originally appeared to me to be a briar patch of curly braces, grown on a quicksand of shifting scope and shattered dreams. That’s a bit much, but I am well aware that “Man, I wish someone would make it so I could use JavaScript syntax and features in Ruby,” is something no one has ever said in a gasp of exasperation or any other kind of gasp for that matter. I am also fully aware that people would rather have a more Ruby-like JavaScript instead of a more JavaScript-like Ruby, thank you very much.
As an apprentice at DevMynd it is my sworn duty to master every aspect of web development, so after I retrieved all five pieces of the Jade Monkey (one from each corner of the world along with another that was in my heart all along), I started working on my JavaScript skills. I was also reading up on some of the finer details of Ruby at the same time and well, you know how it goes: You’re reading about singleton classes in Ruby then working on some JavaScript and then you realize you can just put your chocolate in your peanut-butter or vice versa. Then after seconds of researching candy online you realize someone has beaten you to the punch and you decide to implement JavaScript objects in Ruby.
How then?
There are a couple of classes that already share some similarities with JavaScript objects that we could inherit from: Hash and OpenStruct. I decided to go with Hash for several reasons.
- Hashes have a ton of built in methods that I could easily wrap up.
- Plus, making this work is supposed to be a learning exercise and knowing a lot about the common Hash will come in handy more often the knowing a lot about the seldom used OpenStruct.
- Hash haters might point out that OpenStructs can already dynamically add attributes but they only have the
[]
and[]=
methods in Ruby 2.0, which would leave out all of the old school people on Ruby 1.9 or make for a much more complicated implementation.
Even though we know where we’ll be getting a lot of our methods from, we should not just do this: class JsObject < Hash
. Although I’ll admit that that’s what I did originally, it’s a bad idea.
Thinking ahead, we know we want prototypal inheritance but we also know that we can’t end up delegating to prototypes forever. So since we know all good things must come to an end (I just tricked you into admitting there’s a good thing about JavaScript. Admitting it is the first step), we know there needs to be a final object in the inheritance chain that is essentially a JavaScript object but without inheriting anything itself. This object was creatively named (by me) “Prototype.” It is a subclass of Hash and ultimately a parent to the also creatively named (by me) “JsObject.” Also, I went with inheritance instead of mixins here since they all satisfy the “is a” heuristic (ie a Prototype is a Hash and a JsObject is a Hash and a Prototype, etc). This is to the point that you could set a Hash as JsObject’s prototype and nothing would break (you’d miss out on some cool features though).
Now we can really dive into the Prototype class. If we want object.name = "Hubert"
to work but without defining every possible getter and setter method (which takes about as long as you’d think), we’re going to have to implement method_missing
.
def method_missing(method, *arguments, &block) if equals_method?(method) self[setter_to_getter_name(method)] = arguments.first elsif block self[method] = block else self[method] end end
Nothing too fancy in there. If it’s a method that ends with a =
or if it comes in with a block, it gets delegated to []=
, otherwise it’s just a call to regular old []
. []=
is actually a wrapped version of the normal Hash []=
. In the wrapped method there’s a call to define_methods
, which looks like this:
def define_methods(method_name, value) define_setter_method getter_to_setter_name(method_name) unless respond_to? method_name if value.kind_of? Proc define_proc_getter_method method_name, value else define_getter_method method_name end end
In there I’ve set the setter (say it five times, fast) method unless
it already exists (for a slight performance boost) and then defined one of two kinds of getter method depending on if the value is a Proc. When it’s not a Proc value the getter method just wraps up a []
call with a particular key while Proc values are actually defined as singleton methods on the object.
def define_getter_method(method_name) define_singleton_method method_name do self[method_name] end end def define_proc_getter_method(method_name, proc) define_singleton_method method_name, &proc end def define_setter_method(method_name) define_singleton_method method_name do |new_value| self[setter_to_getter_name(method_name)] = new_value end end
Now the Prototype class has some nifty behavior, but it still needs to implement prototypal inheritance. Also, I apparently sometimes use the word “nifty.” Anyway.
Here’s our method_missing
for JsObject:
def method_missing(method, *arguments, &block) return super if equals_method?(method) delegate_to_prototype method, arguments, block end
The main difference is the call to delegate_to_prototype
. That looks like this:
def delegate_to_prototype(method_name, arguments, block) prototypes_value = prototype[method_name] if prototypes_value.kind_of? Proc define_singleton_method :__proto_proc, &prototypes_value __proto_proc *arguments, &block else prototypes_value end end
It looks a bit scarier but it’s really pretty simple. We grab the prototype’s value and return it unless it’s a Proc. If it is a Proc, we define it as __proto_proc
and then call it with arguments and a block if any. This way we execute the method in the context of the object without defining it as part of its public interface. (It’s true it’s not private
but it is private if you just mind your own business. And besides, nothing’s really “private” in Ruby anyway. Remember send
?)
Anyway, all that’s left to do is to make sure that our JsObjects actually have a prototype value to delegate to. We can take care of that in initialize
:
PROTOTYPE = Prototype.new # ... def initialize(prototype=nil) #... self[:prototype] = prototype || PROTOTYPE end
Unless specified, each JsObjects’ prototype is PROTOTYPE
and because it’s being set with the wrapped []=
method we also get setter and getter methods. Hurray! Now we can get at the PROTOTYPE
without having to type code that looks like it’s yelling.
You can also set your JsObjects’ prototypes to be whatever you want as long as it implements []
: Another Prototype instance, another JsObject, a regular Hash, an OpenStruct in Ruby 2.0, pretty much whatever you want. Of course, setting it to a Hash or OpenStruct eliminates some of the cool features we’ve just implemented, which we will now be taking a tour of.
Strap in.
Or not, it’s really up to you.
Okay, so now we can define attributes on the fly:
js_object.something = "something" js_object.something # => "something" new_prototype = Prototype.new js_object["prototype"] = new_prototype js_object.prototype # => new_prototype js_object["prototype"] # => new_prototype
We can define methods on the fly:
some_proc = Proc.new{ |x| x * x } some_lambda = ->(a){ a + a } js_object.square = some_proc js_object.square(3) # => 9 js_object.double = some_lambda js_object.double(2) # => 4 js_object.number = 5 js_object.cube{ self.number * self.number * self.number } js_object.cube # => 125 js_object.double_again(&some_lambda) js_object.double_again(3) # => 6
And we have prototypal inheritance:
js_object.prototype = PROTOTYPE other_js_object.prototype = js_object js_object.prototype.new_method{ "does hip stuff in the context of #{self}" } js_object.prototype.new_method # => "does hip stuff in the context of " js_object.new_method # => "does hip stuff in the context of " other_js_object.new_method # => "does hip stuff in the context of " js_object.new_attribute = 5 js_object.prototype.new_attribute # => nil js_object.new_attribute # => 5 other_js_object.new_attribute # => 5 other_js_object.newer_attribute = 16 js_object.prototype.new_attribute # => nil js_object.new_attribute # => nil other_js_object.new_attribute # => 16
Through this exercise we’ve managed to do a few things:
- Explore Ruby’s flexibility and leverage it to recreate a feature of another language.
- Show that JavaScript actually has at least a couple of cool features.
- Got you to admit the previous point. Or at least read it.
- Ironically avoided any and all physical exercise.
What are you hiding from me?
Someone is awfully paranoid (you), but is still correct. I skipped over some stuff I thought was a bit boring. I also didn’t show you that Prototype and JsObject are indifferent to symbols and strings without inheriting from HashWithIndifferentAccess (although that’s what I did originally), because that’s another rodeo.
I gemified all of this. Check it out on Github and RubyGems.
What about wrapping up some built in Hash methods?
I did wrap up delete
but I haven’t really wrapped anything else up. Except this blog post.