I’ve been working a bit on a rails plugin for debugging html templates that we use at “Shopify”:http://www.shopify.com/ called “partially_useful”:https://github.com/phoet/partially_useful.
It’s a very simple helper that adds HTML-comments to the rendered source, so that you can inspect it easily in your browser’s developer toolbar. The comment hints look like this:
<-- start rendering 'some_partial' with locals '[:all, :assigned, :locals]'-->
<-- end rendering 'some_partial' with locals '[:all, :assigned, :locals]'-->
There are way more sophisticated plugins out there like “xray-rails”:https://github.com/brentd/xray-rails, but I prefer to have very simple tools that can be used without installing plugins or the like.
I used the plugin in various projects and ported it from “Rails 3”:https://gist.github.com/phoet/905286 to “Rails 4”:https://gist.github.com/phoet/1386152, but it has never been more than a GIST on GitHub. Taking the time to actually convert it into a “Railtie”:https://blog.engineyard.com/2010/extending-rails-3-with-railties/ did not take long and allows everyone to use this plugin in their Rails projects.
All it does is intercepting the
ActionView::PartialRenderer#render method, taking the return value of the call and “wrapping it in HTML comments”:https://github.com/phoet/partially_useful/blob/c482c58ab87400f1b6cadaaed7f0c57003803a1c/lib/partially_useful/partial_renderer.rb#L8-L15.
The tricky bit is how to inject this functionality into the original Rails code…
h2. shoot yourself in the foot way
Just open up the
ActionView::PartialRenderer class and overwrite the method!
While this might seem like the obvious way to do it, it makes everyones life worse, because it makes it much harder to find out where the f**** this new behavior is coming from…
The nicest solution is to use
Module#prepend. It allows you to inject a Ruby module in the first position of the inheritance chain. By doing this, your new render-method will get called and you can just pass everything along to
super aka the original method.
The big downsides here are that this is not compatible with Ruby 1.9 and JRuby.
This is a feature that “Rails adds to Module”:https://github.com/rails/rails/blob/fa03e37d31d7c69c90d41f0764c374318d8dced0/activesupport/lib/active_support/core_ext/module/aliasing.rb#L23-L43 and that allows you put another method in the call-chain by aliasing methods. I’m not a huge fan of it as I find the API rather confusing and it just works with Rails.
h2. bare metal
If you include a Module that has a method with the same name as the method in the target class, the method of the Module will not get called when you invoke it on the class. This is because the methods of the class take precedence in the method-dispatch over those of ancestors in the inheritance chain (hence the
Module#prepend). Because of that, you have to make way for the new method so that it can be invoked properly when you include a Module to override a method.
A way to do that is to use the
Module#included callback to alter the class and alias the old method and remove the original afterwards
klass.send :alias_method, :original_rails_render, :render
klass.send :remove_method, :render
Meta-programming is hard, especially if you want to support multiple Ruby runtimes…