In a recent post, I shared a how-to guide on upgrading Ruby on Rails based on my team’s experience in doing so for the Civis Platform. In this post, I’ll share some of the tips and lessons learned around code implementation, third-party libraries, and testing. Hopefully, they’ll help you make your next upgrade easier than your last.
Just because a language like Ruby does not easily support calling private methods, it does not mean you cannot do so. So be careful when ignoring these protections that Ruby and other languages place on private methods.
However, we don’t recommend using private or protected methods. They’re not part of the public interface, so developers are free to change, break, or even remove them at-will during any release. This can break your application, and you’ll have to rethink the problem. Also, keep in mind: just because the method appears in their documentation does not mean it is public. Read the headers or look for the symbols that indicate whether the method is public or private.
Try to use Rails and other libraries the way their creators intended. Ruby and languages like it are purposefully flexible—there are often many different ways of accomplishing the same task. That said, we still recommend following the documented interfaces and examples. Sometimes, library developers will only test the “happy path,” or documented way, so deviating from this path can lead to chaos in future versions.
Don’t monkey patch large or important pieces of functionality! Yes, it’s easier said than done—but when we upgraded Rails, a lot of our incremental fixes dealt with monkey patches that were written months or even years prior. They often touch internals and private APIs that may change. Not only might they stop working when implementation details change, but worse, they might continue working but behave incorrectly. If you do encounter problems with existing monkey patches while upgrading, think about how to refactor the code to eliminate the monkey patch. Trust me, it will save a future developer’s time when they don’t have to update your monkey patch as part of the next upgrade.
Third party libraries/gems
Again, it’s easier said than done, but upgrading your third-party libraries regularly can pay huge dividends. Many of our incremental fixes were upgrading other gems through multiple major and minor releases to be compatible with the new version of Rails. Given that upgrading Rails often involves upgrading many other gems, it’s important to try and keep up to date with even the smaller libraries.
You can use a tool like gemnasium to ensure that your gems are up to date so that you minimize the number of releases you ever have to update through. We also use RubyAudit, which complements bundler-audit, in order to notify us immediately about any security vulnerabilities.
The bottom line is you want to minimize the number of software upgrades you ever have to make at once, especially when updating something as large as Rails. Since many other libraries you use are closely coupled to Rails, you’ll want to make sure you’re keeping those up to date as well.
Avoid smaller, less-supported gems. It’s great when the libraries you depend on have an active community around them to address bugs and add new functionality, as Rails and other software does too. When you use less-popular or inactive libraries, you should know that they carry the risk of falling into disrepair. If no one is looking out for it, there won’t be new features or support for security and future versions of dependencies.
There are a couple of things to look for when deciding if a gem is well-supported:
- Does the repository have a large and spread out number of contributors? Even if a repository has many contributors, it could be problematic if the vast majority of commits come from the same couple of users.
- How often are new contributions accepted and merged? If the last commit happened a long time ago, future work might never happen. How quickly are new issues and feature requests addressed? Fast response times and active conversations are good signs pointing to an engaged community around the library.
I won’t say you should never use an unpopular gem, especially if it fits your specific needs, but be aware of the potential cost of having to refactor them out in the future. We had to rewrite an entire module of our platform because a gem we were using was no longer functioning in the updated version of Rails, and there were no active plans to move it forward. Using this gem was the right choice for us at the time, but we paid a price when upgrading.
Always write tests! I know it’s cliche, but when upgrading something like Rails in a large code base, there is no way you or your team will be able to manually test everything or click through the entire functionality of your application. You need to rely on your tests, and even the simplest of unit tests can help expose broken functionality during an upgrade.
Testing the functionality and integration between your code base and that of a third party gem’s can be very valuable. As you will have to update your gems, they may introduce breaking changes that may or may not be visible in those release notes or change logs. Ensuring that your application can integrate with other libraries’ can be extremely valuable when updating software dependencies.
Furthermore, tests help a developer working on the upgrade (like me!) figure out what is going on. They helped me figure out exactly what a section of code was intended to do, which was a huge help when trying to refactor broken functionality. Even if this ends up not applying, tests are helpful for ensuring we maintain feature parity during the upgrade.
If you keep these things in mind, and apply them as much as possible, I promise it’ll pay off. I hope you find this helpful, whether you’re upgrading Rails or any other large piece of software in your application. Happy upgrading!