RoR plugins are a great way to get small chunks of useful functionality. I currently use some great ones like acts_as_authenticated, authorization, acts_as_taggable, and friendly_identifier. By the time anyone actually finds this post, I’ll probably have included half a dozen more.
Unlike the compiled world where you grab a library and never see the source, with Rails plugins you have access to the source from the start. This is a great thing, as very often, the plugins are buggy or incomplete. They do 95% of what you want, but not everything. Or, you have a weird setup the author never imagined and his/her test cases do not accurately represent your application. With a compiled library, like a JAR, you are screwed at this point.
In RoR, you can just go to the plugin directory and start hacking away until things work the way you want. Most of the plugins I have are very simple, comprising less than 50-100 lines of code total. Instead of providing complicated functionality, they provide a clean, simple way of solving a single problem. So, once I understand how it’s done, I can modify them to support my particular needs.
Unfortunately, once you start editing the code, you have effectively forked a new distribution. For most people, this involves checking the modified code into your SVN repository and going on your merry way. The problem arises when updates are published to the plugin. What do you do? Manually copy the changes from the new version into your code? Delete your version and re-run script/plugin install? Just ignore the updates? If only there were a way to automatically include the updates from the new version, while at the same time preserving the changes that you added yourself!
Luckily, there is a way to do this, called vendor branches. The entire methodology is detailed in the online SVN book in the vendor branches chapter. The goal is to include third-party code in your project, allow you to make modifications, yet make it easy to include a new version when released. If you want the whole story, read the book, since I will just give quick instructions for RoR people.
1. Prepare your repository
Create a top-level directory (next to trunk, tags, and branches) called vendor. Then, create a subdirectory for your plugin in the vendor directory. Finally, create a current subdirectory in that plugin. Afterwards, your repository structure should look something like:
/myrepo
/ trunk
/ tags
/ branches
/ vendor
/ some_plugin
/ current
2. Get the source
Download the source for the plugin you want into the vendor/some_plugin/current directory. This can be done by inflating a .tar or unzipping a .zip or whatever. I prefer to export the latest SVN revision of whatever plugin I’m downloading (assuming it’s in someone’s svn repo) and then copying the files into the vendor/some_plugin/current directory. Note: If you do this, make sure you export and not check out. Exporting strips out all the .svn folders which you definitely do not want at this stage. I don’t know how to do an export using command-line svn, since I use a client called SmartSVN.
3. Commit the current
Add and commit all the source files into your repository. At this point, you have all the plugin code in your repository inside the current directory.
As a tip, I like to add the original repo and the revision to this commit message. It usually looks something like this:
Vendor drop of some_plugin.
repo: http://someguys/svn/repo/some_plugin
rev: 1234
4. Tag the revision
Create a tag in the vendor/some_plugin directory. This is accomplished via an SVN URL-URL copy (the default copy). So, you copy the vendor/some_plugin/current to /vendor/some_plugin/1.0 (or whatever version you downloaded). If there are no tagged releases of the plugin, I name the tag after the revision that I got. For example, /vendor/some_plugin/rev_1234
Directory structure:
/myrepo
/ trunk
/ tags
/ branches
/ vendor
/ some_plugin
/ current
/ 1.0
5. Copy the tag to your trunk
Copy the tagged source to your trunk, into the vendor/plugins directory of your RoR app, using the plugin’s real name as the final directory. Again, this is accomplished by an SVN URL-URL copy.
Directory structure:
/myrepo
/ trunk
/ my_rails_app
/ vendor
/ some_plugin
/ tags
/ branches
/ vendor
/ some_plugin
/ current
/ 1.0
At this point, the copy in the trunk is based on the 1.0 tag in the vendor branch.
6. Edit to your heart’s content
You’re done! Now you can work on your trunk, including the new plugin. If you need to modify it in any way, just do it. Make your changes and commit. Do this as much as you need. Essentially, you can treat the plugin like it’s your own code. One caveat: Do NOT make any changes to the code in the top-level vendor directory. This is only for holding pristine releases from the third-party.
7. When a new version is released
The whole point of all this mumbo-jumbo was to support including features of a new release. When that happens, repeat steps 2-4 with the new version, overwriting /vendor/some_plugin/current with the new release. After that, current in the vendor branch will point to the new release, and there will be a new tagged branch (ie. /vendor/some_plugin/2.0)
At this point, you have 1.0 and 2.0 in your repository, and SVN knows the differences. So, you can do a merge from 1.0 to 2.0 and apply the results to your trunk, in the vendor/plugins/some_plugin directory. SVN will do a diff between 1.0 and 2.0, figure out what changed, and apply those changes to your trunk. As long as your edits do not conflict with the changes, it is seamless. Re-run all your tests to make sure the new version hasn’t broken anything, then commit. At that point, you have upgraded to 2.0, but retained all the modifications you made. Plus, it’s very easy to revert back to 1.0, as it is a simple rollback.
Update (2007-05-18): We just executed a vendor branch upgrade. Take a look at the detailed instructions on how to go about upgrading a vendor branch.
Spending an extra ten minutes going through the motions of a vendor branch can be frustrating when all you want to do is get your plugin running now! Still, investing that ten minutes can save you hours of pain when trying to integrate a new version in the future. Plus, there is just a certain sastisfaction to be had in knowing that you are doing things the right way.
Recent Comments