Yehuda Katz is a member of the Ember.js, Ruby on Rails and jQuery Core Teams; he spends his daytime hours at the startup he founded, Tilde Inc.. Yehuda is co-author of best-selling jQuery in Action and Rails 3 in Action. He spends most of his time hacking on open source—his main projects, along with others, like Thor, Handlebars and Janus—or traveling the world doing evangelism work. He can be found on Twitter as @wycats.
Using >= Considered Harmful (or, What’s Wrong With >=)
August 21st, 2010
Having spent far, far too much time with Rubygems dependencies, and the problems that arise with unusual combinations, I am ready to come right out and say it: you basically never, ever want to use a >= dependency in your gems.
When you specify a dependency for your gem, it should mean that you are fairly sure that the unmodified code in the released gem will continue to work with any future version of the dependency that matches the version you specified. So for instance, let’s take a look at the dependencies listed in the
activemodel (= 3.0.0.rc, runtime) activesupport (= 3.0.0.rc, runtime) builder (~> 2.1.2, runtime) erubis (~> 2.6.6, runtime) i18n (~> 0.4.1, runtime) rack (~> 1.2.1, runtime) rack-mount (~> 0.6.9, runtime) rack-test (~> 0.5.4, runtime) tzinfo (~> 0.3.22, runtime)
Since we release the Rails gems as a unit, we declare hard dependencies on
activesupport. We declare soft dependencies on
You might not know what exactly the
~> version specifier means. Essentially, it decomposes into two specifiers. So
~> 2.1.2 means
>= 2.1.2, < 2.2.0. In other words, it means “2.1.x, but not less than 2.1.2″. Specifying
~> 1.0, like many people do for Rack, means “any 1.x”.
You should make your dependencies as soft as the versioning scheme and release practices of your dependencies will allow. If you’re monkey-patching a library outside of its public API (not a very good practice for libraries), you should probably stick with an
One thing for certain though: you cannot be sure that your gem works with every future version of your dependencies. Sanely versioned gems take the opportunity of a major release to break things, and until you have actually tested against the new versions, it’s madness to claim compatibility. One example: a number of gems have dependencies on
activesupport >= 2.3. In a large number of cases, these gems do not work correctly with ActiveSuport 3.0, since we changed how components of ActiveSupport get loaded to make it easier to cherry-pick.
Now, instead of receiving a version conflict, users of these gems will get cryptic runtime error messages. Even worse, everything might appear to work, until some weird edge-case is exercised in production, and which your tests would have caught.
But What Happens When a New Version is Released?
One reason that people use the
activesupport >= 2.3 is that, assuming Rails maintains backward-compatibility, their gem will continue to work in newer Rails environments without any difficulty. If everything happens to work, it saves you the time of running their unit tests against newer versions of dependencies and cutting a new release.
As I said before, this is a deadly practice. By specifying appropriate dependencies (based on your confidence in the underlying library’s versioning scheme), you will have a natural opportunity to run your test suite against the new versions, and release a new gem that you know actually works.
This does mean that you will likely want to release patch releases of old versions of your gem. For instance, if I have AuthMagic 1.0, which worked against Rails 2.3, and I release AuthMagic 2.0 once Rails 3.0 comes out, it makes sense to continue patching AuthMagic 1.0 for a little while, so your Rails 2.3 users aren’t left out in the cold.
Applications and Gemfiles
I should be clear that this versioning advice doesn’t necessarily apply to an application using Bundler. That’s because the
Gemfile.lock, which you should check into version control, essentially converts all
>= dependencies into hard dependencies. However, because you may want to run
bundle update at some point in the future, which will update everything to the latest possible versions, you might want to use version specifiers in your
Gemfile that seem likely to work into the future.