If you’ve ever had to override operators in Ruby via some alias_method_chain magic, you’ve probably run into the scenario where alias_method_chain breaks with a syntactically invalid set of aliased methods. A simple way to reproduce with Hash (don’t ask, I needed to mess with the Hash-derived CookieJar in Rails for Groupon’s cookie handling. The details shall remain a mystery to everyone but my team and I!).
# in a rails console Hash.class_eval do # define the method (this will work, but you won't be able to use it because of the bogus syntax) define_method "[]_with_printing=" do |key, value| puts "Assigning #{value} to self[#{key}]" send "[]_without_printing=", key, value end # this will not work, because of the bogus syntax alias_method_chain :[]=, :printing end
Thankfully, the wizard-like omniscience of the Rails core team predicted such a dilemma and added a block-syntax to alias_method_chain. I have violated just about every sacred tenant of metaprogramming in my career. To discover this after years of those atrocities, I was dumbfounded, bewildered, and flabbergasted at the fact that this functionality escaped my awareness (which beams out at the Ruby landscape like Sauron’s Eye, except tuned to gems and not rings). The solution is amazingly simple (although, it has a slightly distasteful stateful element).
Hash.class_eval do # a nice no-conflict name def hash_assignment_operator_with_printing=(key, value) # unless you have a REALLY specific ordering reason, put your extended code before the original method # allow the original return value to pass through unhindered. Remember, we're like software anthropologists here! puts "Assigning #{value} to self[#{key}]" send(:hash_assignment_operator_without_printing=, key, value) end alias_method_chain :[]=, :printing do |aliased_target, punctuation| aliased_target.sub!("[]", "hash_assignment_operator") # yes, I know, this is destructive/side-effecty. I don't like it either end end test_hash = {} test_hash[:test] = "value" # should puts our statement # assert_equal(test_hash[:test], "value", "hash assignment should not be affected by :printing functionality"
The only thing I would advise is to tightly scope these kinds of hacks. You DON’T want to literally jam this stuff into Hash. Subclass Hash for your specific needs and add this functionality there, at the very least. I could go into a long diatribe of why that causes all kinds of pain, or how we shot ourselves in the foot early on at Groupon by hacking a little too-wide. Just consult your nearest MartinFowler-like guru. (He|She|Both) will certainly have an eloquently stated reason to avoid excessive generality in your meta-programmed adventures. Think stomping c-methods, or unexpected behavior for some poor joe-schmoe writing a simple hash-using utility.


