TRANSCODE Explorations into the Code Transcendental.

Gem Crazy

Gem Crazy

I believe the Ruby community may have finally gone off the rails in it’s collective crazy train. I have been reading about Bundler, the spiffy new RubyGems manager to be default part of Rails 3.0, and I can’t help but wonder what track we were even on.

If you don’t know about Bundler yet, you can learn about it here. I imagine most of you are already aware of it and are now considering how it fits in with your future development plans. So I want so ask a more fundamental question: Why does Bundler even exist?

On the surface, we can of course thank Rails. The deployment of web applications stands apart from the distribution of Ruby packages. Deploying a Rails application usually means copying an app’s files up to a server, as opposed to creating a distributable package file (like a gem) and installing that on the remote machine. Technically the later could be done, but it isn’t, probably because most web apps are proprietary and do not need wider distribution. Because Rails developers do not use the normal RubyGems distribution mechanism, which automatically installs dependencies, they need another way to ensure their apps dependencies are present, hence the convenience of bundle install.

Bundler achieves this goal in a grand fashion. As its name implies it does so by giving us the means to download the gems an application is dependent upon and store them directly within the project itself. These gems get stored in a project’s vendor/gems/ directory. The rationalized benefit being that you need never worry about remote services going down (e.g. rubygems.org) in order to deploy your application, nor ever worry about any potential changes in a dependent gem. So in brief, one can bundle dependent gems with ones application.

At first blush it seem like great idea. After all developers use vendoring to do something similar while developing an application. But lets consider this a bit more carefully. The worry of a remote service going down is, honestly, bordering on paranoia. That’s why mirrors of RubyGems.org exist. If one goes down, the others remain. The odds of all mirrors going down at the same time are extremely small, and as you might imagine, such a situation would be corrected very quickly, if it ever did occur. In fact, if it did happen, there is a fair chance your host will be down too. So the solution here is not bundling, but redundancy. As for the fear of “monkey releases” –new versions of a gem that that might break things, this is somewhat understandable, but redistributing every dependency with each app is shear overkill. If you are that concerned a gem will break an application you can always specify exact requirements in the gemspec, and use the gem method to nail the version down. That, in fact, is the whole point of the gem method. And it is a perfectly good solution for applications such as a Rails app. Unfortunately is not a very good solution for general purpose libraries. (As can be read about in any number of blog posts by searching Don’t require ‘rubygems’.)

[Update: I have been informed that the real benefit of bundling is to make it easier to install an application on machines that has limited Internet access. In such cases, I can see that Bundler might be handly.]

I should mention that the latest version of Bundler (v0.9) has a light-weight rendition of bundling, called locking, which doesn’t actually store the gem in the application, but automatically locks the gems your application uses to specific versions. In other words, it generates a file which (I assume) will ulitmately be used with the gem method to nail down the exact versions of gems your application is using at that time of the lock. This is a much better approach than bloating your application with .gem files, to achieve the same end, and to Bundler’s credit it makes it easy to keep those gem calls current. However, it still ties your program to RubyGems, so again it’s not a perfect solution for reusable libraries.

Another feature Bundler sets out to handle are groups. They are “gem environments” which can be loaded as a collective, or even omitted during an install. In other words, they are a way to specify a specific group of gems to the bundle command or in ones code with Bundler.require. The most common groups you are likely to see are runtime or production, test and development, patterned after Rails own database environments. While I will not dismiss out-of-hand the potential of deriving some utility from this feature, on the whole I suspect it is a YAGNI. Moreover, it exposes the developer to even more potential deployment errors. I can only imagine the failures that will arise because a testing environment had a gem that the runtime environment forgot to add, but actually needed.

In addition, the use of Bundler promotes the use of Bundler.require. How many countless hours have been spent reminding people not to put require 'rubygems' in their lib code? Now the issue will be doubly compounded. While we can hope that Bundler never makes it’s way beyond Rails (or at least end-user apps), I have already seen it in at least two non-Rails programs.

Lastly, Bundler also provides an ability to depend on git repos, so long as they track a .gemspec. This feature is a clear address to vendoring, but via gem dependencies. I won’t even go into all the issues that arise with this (and seems to somewhat contradict Bundler’s primary purpose –or at least compound its need!). You can read some of the back and forth on this idea here. I will say, however, what really strikes me most about this is how an unproven system takes on such fanfare that it’s proponents now declare we have all been doing it wrong! A .gemspec should never be generated by some “extraneous build tool”, but ought to be edited by hand and checked into the scm repository. Wow.

I realized the Bundler is addressing certain needs, primarily of the Rails community. The problem with Bundler is that it is trying to correct something that is fundamentally problematic from the start. On two fronts… The first being that RubyGems ties a packaging system to versioning, so using the gem command will never be a universally applicable solution to lock down versions. And the second, more significant issue, that RubyGems, and hence Bundler, cannot avoid version conflicts. RubyGems blows-up when it encounters a conflict. Bundler only mitigates this issue by selectin suiable version before runtime. But niether allow different versions to run simultatneously. This is the real heart of the matter, and it stems from the fundamental lack of version support in Ruby itself. In my opinion, until that is addressed, all of these fancy solutions are simply trains headed up the wrong track.


title : Gem Crazy author : trans categories : [ruby, rubygems] date : 2010-05-15 layout : post