We all love to Monkey Patch Rails and other Ruby apps. However, we sometimes want to target these patches to the specific versions where they are needed.
Here’s the easiest way to do this, via RubyGem’s built-in version requirement support. The version
0.11.0 should indeed be greater than version
irb(main):001:0> require 'rubygems' => true irb(main):002:0> Gem::Version::Requirement.new(['> 0.9.0']).satisfied_by?(Gem::Version.new('0.11.0')) => true
Notice that you can’t do this with string comparison, because with a per-character comparison,
1 is not greater than
irb(main):001:0> '0.11.0' > '0.9.0' => false
Here’s a little class which puts some helper and example methods around this approach (these methods are all in real use for some of our multipart mailer hacks):
module Pivotal class VersionChecker def self.current_rails_version_matches?(version_requirement) version_matches?(Rails::VERSION::STRING, version_requirement) end def self.version_matches?(version, version_requirement) Gem::Version::Requirement.new([version_requirement]).satisfied_by?(Gem::Version.new(version)) end def self.rails_version_is_below_2? result = Pivotal::VersionChecker.current_rails_version_matches?('<1.99.0') result end def self.rails_version_is_below_rc2? Pivotal::VersionChecker.current_rails_version_matches?('<1.99.1') end def self.rails_version_is_1991? Pivotal::VersionChecker.current_rails_version_matches?('=1.99.1') end end end
(note: some angle brackets changed due to code formatting bug)
Here’s an example of how you’d use this:
if Pivotal::VersionChecker.rails_version_is_below_2? # do some backward compatibility stuff # or handle bugs that have been fixed in Rails > 2 end
Note that this is only possible now that Rails has started using a more sensible strategy for versioning edge gems and improved support for using advanced versioning with RAILS_GEM_VERSION.
For many projects, this may be overkill. It is useful at Pivotal, though, where many various projects may be on different rails versions, but still want to use the latest common core libraries (and monkey patches) without having to upgrade Rails for their app.
This isn’t only useful for monkey patching. It can be handy for any library that wants to be backward- or forward-compatible with its dependencies. I’ve used this approach at Pivotal and on my personal projects to have Continuous Integration automatically run my tests against multiple dependency versions, without having to change anything other than the CI project name:
There are numerous other related topics for discussion in this area, such as the power of versions or the wisdom of freezing, but I’ll save those for future posts. Even if you do freeze the trunk of Rails/plugins/gems, since the version is included in the source, this approach should work barring any conflicts with trunk changes since the last release.