Thanks to all who asked for an article about “freezing” your Rails application. I’ll try to cover all the basics, but
feel free to follow up with more questions by leaving a comment below.
Talking about “freezing” your Rails app won’t make sense if you don’t first understand what Ruby Gems are all about.
Even if you’re new to Rails, you already know at least a little bit about gems, because you can’t install Rails without learning this command:
gem install rails
sudo gem install rails if you’re on Mac/Linux).
Gems are the official way to share Ruby code with your friends, coworkers, and enemies. The RubyGems system is accessed with the
gem utility, like we just did when we installed the Rails gems. In fact, for us Windows users, RubyGems is kind of like your new version of the Add/Remove Programs applet that comes with Windows. Using the
gem command, you can install, upgrade, and uninstall Ruby code that you don’t write yourself.
Imagine a world without RubyGems. You write some cool Ruby class and want to send it to me. You could just email it, I guess. I’d have to copy it onto my machine, and then
require it from within my code, using the full path to wherever I copied it to. Not a big deal, I suppose. If many people sent my their Ruby code, I’d probably create a folder for all of “everyone else’s stuff” and put it all in there.
But it could get worse. Instead of one .rb file, you want to send me your whole Ruby code library, spread across 9 different files. Or you’d probably zip it up into one file. Now this is starting to become a hassle. Because not only do I need to remember where I’m keeping all this code, I’m sure that the very next day you’re going to say you fixed something and you’re going to want me to upgrade to the newest stuff. Oh and it depends on this other zip file from this other guy, so I have to make sure I have that on my computer, too.
A Gem is like a zip file of Ruby code, except it also gets stamped with metadata about the code inside. Like who wrote it, a version number, and if it depends on any other gems. Instead of .zip, it gets an extension of .gem, and it’s the standard package format for third-party Ruby code. It makes it easy for us to all share code.
gem utility can automatically find gem files in well-known locations on the internet, download them for us, upgrade them, and remove them. If you’ve never done it, do a
gem list right now to see what gems you have installed.
Like it or not, every Rails application you write depends upon the presence of certain gems having been installed on the same computer that’s running the application. Those gems are the Rails gems of course. Rails is a gem that depends upon other gems like activerecord, actionpack, activeresource, activesupport, actionmailer, and rake. (The gems that you must have to run every Rails app are: rails, activerecord, actionpack, and activesupport. Actionmailer and activeresource can be omitted if you’re not using those features.)
This means that when you publish your application on the internet – that is, you’ve deployed it to some server – that server also better have those gems installed, or your application will fail pretty quickly. This is why, if you’re looking at shared hosting providers, you have to find out if they support Rails, which really means do they have the Ruby interpreter and the necessary gems installed.
So if you’re with me so far, you know that you have some gems installed on your development box that make your Rails app work, and there are gems on the server that make your app work there, too. Good, right?
No. Not good.
Very, very bad.
It turns out that you can have multiple versions of a gem installed on the same machine. There’s a line in your
environment.rb file that connects your application to a specific version of the Rails gems, and it looks something like this:
# Specifies gem version of Rails to use when vendor/rails is not present RAILS_GEM_VERSION = '1.2.3'
In this example, the application will look for the 1.2.3 version of Rails. If your server has it, all is good. If not, ouch.
So theoretically, all you have to do is always keep all versions of Rails that you need for all of your applications.
On every server.
All the time.
Not good. And probably not even possible if you’re under a shared hosting plan (or if you’re not the boss of your IT department).
By now, I hope you now understand the problem.
Here’s the solution. Instead of having your application search the system-wide gems for the appropriate gems, you can “override” that search by placing copies of the desired gems right into a subfolder of your Rails app. If you re-read that line from your environment.rb file, you’ll notice the comment above it, which implies that Rails will first search a subfolder in your Rails app named “vendor/rails” for the Rails gems, before it goes looking elsewhere for them.
All you need to do, then, is copy the gems into your vendor/rails subfolder, right?
Well, no, unfortunately. Technically speaking you have to “unpack” them, not just copy them. Unpacking a gem is kind of like unzipping a zip file. The gem utility provides an
unpack command to do just that. So you have to do unpack each Rails gem that your app needs into the a subdirectory heirarchy under vendor/rails.
You can do all that by hand, if you want to. Fortunately, Rails comes with a prewritten
rake task to it all for you.
Freezing your application will make a copy of your system-wide gems and unpack them into your Rails app. As always, run rake tasks from the “root” directory of your Rails application:
c:\dev\myapp> rake rails:freeze:gems
You’ll now find
c:\dev\myapp\vendor\rails has been created and populated with the latest version of your Rails gems. You can now deploy your Rails app anywhere regardless the version of Rails has been installed on the server. Your application will always use the gems you’ve frozen to it.
You can reverse the process:
c:\dev\myapp> rake rails:unfreeze
This toasts the
vendor/rails directory. Now when your application starts up, it won’t find anything there, and will resort to looking for Rails in the system-wide gem repository instead. Or, you can now re-freeze to your latest and greatest version. (If all you want to do is re-freeze a frozen app, no need to thaw first, just run
rake rails:freeze:gems again.)
You maybe have heard the term “freeze to edge rails.” Turns out, in addition to freezing your application to a released version of Rails, you can download the latest bleeding-edge Rails bits and unpack them into vendor/rails. That means that you’ll be able to get the latest bug fixes and newest features (and newest bugs, too). Since you’re doing this to your application only, it’s not touching your stable Rails gems that you’ve installed system wide. This is a great way to try out the latest features in Rails.
Don’t know how to download the latest Rails code by hand? Yet again, another rake task comes to the rescue (actaully, two):
c:\dev\myapp> rake rails:freeze:edge c:\dev\myapp> rake rails:update
rails command get updated with the latest versions from edge. (Our patch has made this second step obsolete, so in Rails 2.0.3 you won’t have to do the
rails:update step anymore).
I always have an “edge” app on my box, that I just keep re-freezing to edge so I can play with the latest stuff going into Rails.
Rails 1.x doesn’t necessarily freeze well with RubyGems version 1.0 and higher. If get a strange-looking “GemRunner”-related error when you try to freeze, that’s what’s happening. There’s a workaround – google for the solution, or let me know and I’ll try to post an article about that, too.
Also, the same version hassles can occur with any other gems you might be using in addition to your Rails app. Read this for how to solve that, too.
So have fun with edge, and always freeze your apps before deployment.
Questions? Comments? Let us know.