"Monkey Patching" in Ruby
I don't use a whole lot of Ruby in my day to day work, but it is a language that is very quickly growing on me for several reasons.
As I further integrate mojombo/jekyll into my website pipeline, I'm finding more of a need to extend it with plugins, no surprises there. One particular plugin that I was previously using is ixti/jekyll-assets. I have since moved over to matthodan/jekyll-asset-pipeline as it better serves my needs, but this article remains.
Jekyll-assets provides a sstephenson/sprockets based asset pipeline, closely mirroring the pipeline present in Rails. The asset pipeline allows me to quickly concatenate and process multiple JavaScript and CSS files into single output bundles; reducing the amount of requests made when loading a page, and thereby improving load time. I also use it to build CoffeeScript and SASS stylesheets.
What is Monkey Patching?
Monkey patching refers to the ability to modify existing code at runtime and outside of regular means (ie. editing the original source). This can be useful as a way to add often-needed functions to a basic type, and in the case of Ruby where everything is an object, this can even include adding methods to numbers themselves (which are of the FixNum
class), for example:
5.plus10
=> 15
The reason that this is possible to due to the fundamental way that Ruby allows you to "reopen" modules and classes up and modify them at any time, even throwing in full reflection and introspection. Actually taking advantage of this is very easy but also useful in many situations, as I will show you now.
A minor inconvenience.
The jekyll-assets gem returns all asset paths as absolute paths. This works well for 99% of cases, but where it fails is when you want to use the <base href="/" />
tag, which I often use while developing a site contained in a subdirectory of the webroot. Returning an absolute path will cause the browser to ignore the base path and so assets will no longer be loaded from the correct place.
To figure out how to solve this I jumped over to the ixti/jekyll-assets page on GitHub and started digging through the source. Quickly I came across the render_asset_path
method under lib/jekyll/assets_plugin/tag.rb.
The fix?
From here it was obvious that a simple modification would fix my problem. Applying this is as easy as opening _plugins/ext.rb
and adding the following:
_plugins/ext.rb
require 'jekyll-assets'
# Patch jekyll-assets at runtime to use relative URLs.
module Jekyll
module AssetsPlugin
class Tag
def render_asset_path context
with_asset context do |asset, site|
return asset_not_bundled unless site.has_bundled_asset? asset
return "#{site.assets_config.dirname}/#{asset.digest_path}"
end
end
end
end
end
As you can see we've reopened the appropriate modules and class, and are redefining the render_asset_path
method based on the original source. All we have done here is remove the single "/" character from that last return statement. Now when re-running Jekyll we should see the assets are being generated with the ideal paths.
Where to from here?
While reading up on monkey patching I came across the following articles which may be of interest, some of which are for and some against the idea.
- http://www.rubyinside.com/the-end-of-monkeypatching-4570.html
- http://css.dzone.com/articles/why-ruby-monkey-patching
- http://olabini.com/blog/2011/01/safeer-monkey-patching/
As with anything programming related, make sure you understand the advantages and disadvantages of the pattern you're using. In this case monkey patching has been used as a "quick fix" for a problem that is relevant to my use case, but probably not anyone else's, and most likely wouldn't benefit from being contributed back to the source.