00:00:11
Hello, my name is Jordan Raine. I work at a company called Clio, and I'm a developer there.
00:00:18
I've been writing Ruby code and been a Rails developer for about nine years. Our company is based in Vancouver, Canada.
00:00:25
Over those nine years, I've had the chance to upgrade apps from Rails 2 to 3, from 3 to 4, and from 4 to 5.
00:00:34
I can still remember the Rails 2 to 3 upgrades and how many breaking changes there were. It was painful, but thankfully, things have gotten better.
00:00:50
Now, there are fewer breaking changes for each version as we receive updates. We also have deprecation warnings that are becoming clearer.
00:01:02
In many cases, these warnings will tell you exactly what you need to do to ensure that your app will work with the next version of Rails.
00:01:14
In addition, we now have beta periods that can last for months, allowing us to test the newest version of Rails.
00:01:20
Yesterday, when I spoke, the idea was mentioned that Rails isn't dying; it's maturing. Looking at the upgrade path, this certainly seems to be the case on paper.
00:01:33
In practice, however, upgrading can still be really hard. It's hard for us at Clio as well.
00:01:40
After discussing this with other companies, I learned that it has been hard for them too.
00:01:47
I was chatting with someone this week and found out that there are a lot of consultants who step in to perform upgrades for companies because some simply can’t afford to do it or don’t know how to navigate their own codebase.
00:02:05
This led me to question: with all the improvements made by the Rails core team, why is it still tough to upgrade?
00:02:12
I want to explore this issue in four parts, starting with a story of two Rails upgrades.
00:02:23
The first story involves a small startup.
00:02:28
One day, a developer is scrolling through Twitter and sees a tweet from DHH: Rails 5 is out.
00:02:37
She has been meaning to try out Active Storage and, not having much on her plate, decides to upgrade.
00:02:44
She spends the rest of the day upgrading gems, running the tests, making them pass, and sending it off for code review.
00:03:00
The next day, she and another developer work through all the major flows in the app and get it running.
00:03:07
By the end of the day, they ship it. Just 24 hours after seeing that tweet, they're running the latest version of Rails.
00:03:20
The next story features a different company.
00:03:27
This company is larger and has been around for many years, with a big team of developers and thousands of customers who rely on their services.
00:03:44
When they see the tweet from DHH in their Slack channel, they don’t feel excited; instead, they feel dread.
00:03:52
They realize they are now two minor versions behind and one major version behind, running out of maintenance time.
00:04:00
They can’t defer the upgrade any longer and gather a team to tackle the project bit by bit.
00:04:07
As they upgrade their codebase, they try to ship any changes that they can, but the process takes three months.
00:04:20
Once they finally finish, they haven’t deployed a new Rails version in many years, and so they’re uncomfortable.
00:04:30
They decide to schedule downtime just to ensure the deployment goes smoothly.
00:04:39
When they finally deploy, they are now running the latest version of Rails.
00:04:50
It may not seem fair to compare these two upgrades, but the startup only had a couple of gems and a relatively simple codebase.
00:05:05
Their tests were few and the stakes were lower, allowing them to take larger risks.
00:05:17
In contrast, the larger company faced numerous gem upgrades, including some that were no longer maintained.
00:05:30
They had thousands of lines of code to review and many tests, some of which did not even start to work.
00:05:44
When they were ready to deploy, due to the reliance on countless customers, the anxiety was palpable.
00:05:56
Yet, both companies fundamentally went through the same steps: they upgraded their gems, modified their code, tested it, and deployed.
00:06:05
One company had more to do at each step, but the end result was still the same. They were both now running a new version of Rails.
00:06:17
This led me to wonder whether this experience is an inevitable part of growing a company and aging codebase.
00:06:35
That brings us to Clio. Our upgrade paths look a lot like that of the larger company.
00:06:47
We’ve been around since 2008, building a product for lawyers.
00:06:54
When I began to look at our next Rails upgrade earlier this year, I started to ask why this process seemed so difficult.
00:07:05
What have we done in the past, and how can we improve it? I went back to explore our upgrade history.
00:07:19
To bring you up to speed, I’ll give you a quick look at our upgrade path and timeline.
00:07:26
This timeline starts with Rails 2.0 in 2007, which is the version we shipped with when the product first launched.
00:07:34
On the very same day that 2.0 was released, our founder upgraded our app to 2.0.
00:07:43
Halfway through 2008, when 2.1 came out, we again upgraded on the same day.
00:07:51
2.2 came out a couple of months later, and we lagged a little bit, about three months behind.
00:07:58
But we soon caught up. Then 2.3 came out, and we lagged a bit more, nine months this time.
00:08:06
Next came 3.0, followed by 3.1 and 3.2, and in 2012, we felt some pressure.
00:08:15
We upgraded to 3.0, 3.1, and 3.2 over a five-month period.
00:08:25
Then 4.0 was released, followed by 4.1, and then 4.2.
00:08:31
In 2015, we upgraded to 4.0, 4.1, and 4.2 over a six-month period.
00:08:42
This was too fast.
00:08:49
Looking at this timeline, an obvious pattern emerges.
00:08:55
What surprised me, however, was not the times we were actually upgrading in 2012 and 2015.
00:09:05
Instead, it was the gaps in between the years.
00:09:16
I found myself asking what we were doing during those gaps and why we didn't upgrade the app.
00:09:25
When I thought about the business at that time, it turns out that we were actually quite active.
00:09:30
We were growing as a company, hiring developers, building features, and adding customers.
00:09:40
We had started to generate revenue, and our business choices seemed solid.
00:09:51
However, we also made some questionable technical decisions, and I believe the key to easily upgrading Rails lies in these gaps.
00:10:01
For those who haven’t experienced a Rails upgrade project before, let me illustrate what it feels like to be on a Rails upgrade team.
00:10:17
Upgrading your framework impacts you significantly, as you need to pay off technical debt accrued over the years.
00:10:24
When you open your Gemfile, you often find outdated gems that have been left untouched for too long.
00:10:36
It's tempting to add gems when you need specific functionality and then ignore them as time passes.
00:10:44
But problems arise when your codebase ages and those gems start causing issues.
00:10:52
This highlights the principle that dependencies are not free. It's not a new idea; we know this.
00:11:03
There is a fascinating blog post from 2001 by Joel on Software discussing the Excel development team's approach to dependencies.
00:11:14
They advocated eliminating dependencies and took it to the extreme of building their own C compiler.
00:11:23
This seems absurd for those of us working with web apps today, but it was quite effective for them.
00:11:31
The Excel team's mentality of independence meant they shipped on time and maintained uniformly high quality.
00:11:40
They recognized the associated costs of their dependencies and made a conscious decision to manage them better.
00:11:56
While we shouldn't aim to empty our Gemfile of all dependencies, we do need to understand these costs.
00:12:08
It's not practical to build our own web framework; I'd prefer using Rails. But commitment to regular updates is essential.
00:12:20
Failing to keep up with these updates leads to compounded problems. For instance, we integrate with QuickBooks Online using a gem.
00:12:36
QuickBooks Ruby relies on another gem called OAuth, which we had pinned to an older version.
00:12:45
While QuickBooks Ruby worked fine for a while, when we tried to upgrade it to add a bug fix, we ran into trouble.
00:13:00
The new version of QuickBooks Ruby upgraded the OAuth version, which caused it to fail.
00:13:12
This forced us into a dilemma: either we could not upgrade any gem, leave the bug unfixed, or upgrade both gems, blowing up our timeline.
00:13:24
Ultimately, we chose to fork the gem and downgrade the version of OAuth, which seemed like a sensible solution at the time.
00:13:38
While this worked temporarily, it deferred the costs, ultimately coming back to bite us during future upgrades.
00:13:49
Each future upgrade would require going through the fork, merging codes, and updating versions in a cumbersome way.
00:14:02
This made developers less likely to upgrade because it was such a painstaking process.
00:14:09
The more rigid your dependencies are, the harder they are to maintain, which fosters resistance among developers to make necessary changes.
00:14:25
Another thing we've done in those gaps was continuously merge more and more code.
00:14:35
When you ask a developer to implement a feature, they'll often copy code patterns they find within the existing codebase.
00:14:45
If that code includes calls to deprecated methods or undocumented Ruby calls, those bad patterns will spread throughout the code.
00:14:55
Every time this pattern propagates through the code, it becomes harder to clean up.
00:15:02
Some changes can be resolved with simple find-and-replace methods, while others, like layers of protected attributes, are difficult to remove.
00:15:13
When we upgraded from Rails 3 to 4, we added protected attributes to give us temporary relief, but that only compounded the problem.
00:15:27
Even when we removed it from our models, it had affected the entire application.
00:15:39
Moreover, I know I'm not alone in this; many developers have had the same experience as that gem has been downloaded millions of times.
00:15:51
This disconnect from the Rails community often leads us to solve problems that Rails has already tackled in better ways.
00:16:07
This happened during our upgrade to Rails 4.1 when we missed the introduction of Active Record enums.
00:16:19
Instead of utilizing the built-in functionality, we built our own version, leading to multiple similar implementations.
00:16:31
Even when Active Jobs were introduced in 4.2, we skipped it because we had already invested in our own solutions.
00:16:43
Creating your own solutions is fine if it's core to your mission, but doing so just to catch up translates to wasted effort.
00:16:57
Additionally, this complicates onboarding for new developers who might find certain solutions within the codebase unrecognizable.
00:17:10
Having looked into this, I found that these gaps discourage us from performing the upgrades we know we ought to.
00:17:18
But the good news is that we can manage most things needed for Rails upgrades at any time.
00:17:26
You can undertake them in small steps; they don't have to be massive projects.
00:17:38
At Clio, we approached this in three specific ways.
00:17:46
First, we focused on maintaining a healthy Gemfile.
00:17:54
Dropping unnecessary version constraints is a good start, ensuring that your Gemfile accommodates upgrades.
00:18:01
Whenever feasible, consider if a version constraint is truly necessary and remove it when possible.
00:18:10
Second, we eliminated forks which often complicated things even more.
00:18:24
Forks create obstacles for upgrades, increasing the burden on developers, who may not remember the need for them.
00:18:33
Finally, we fostered a culture of eagerly updating gems.
00:18:39
Instead of waiting for a feature or bug fix, we made it a priority to update gems regularly.
00:18:49
Awareness of outdated gems is crucial, and we utilize tools to help with this process.
00:19:02
One such tool is 'bundle outdated,' which provides a line indicating each outdated gem.
00:19:21
However, running this can be overwhelming, especially when numerous gems are outdated.
00:19:32
Inspired by this, we developed our own script called 'bundle report' to better manage our Gemfile.
00:19:48
This reports gems from oldest to newest, giving clearer priorities on which gems need updates.
00:20:01
Next, we focused specifically on Rails upgrades.
00:20:14
By identifying gems that won't work with new versions of Rails and what their acceptable version is, we could manage changes better.
00:20:28
This systematic approach enables us to act proactively to upgrade.
00:20:35
We also focused on deprecations, adhering to the notion that they have been improving over time.
00:20:44
Most major changes can be caught if you're running the latest patch version of the previous Rails version.
00:20:53
In our case, we developed a deprecation tracker that integrates with our tests.
00:21:00
This tracker monitors deprecated methods across our codebase and alerts us during Continuous Integration.
00:21:11
By having control over the deprecated methods, we can keep our coding practices in check.
00:21:27
This provides us substantial insight into our code quality and encourages proactive maintenance.
00:21:35
The last approach we adopted was the concept of dual booting Rails.
00:21:48
First introduced by Shopify, this allows developers to easily switch between versions of Rails.
00:22:00
It's beneficial for testing various configurations with minimal friction.
00:22:15
By making these adjustments, it now feels less daunting to upgrade Rails.
00:22:24
These methods enable us to continuously learn from previous upgrades and lessen the burdens faced with each version transition.
00:22:36
Moving forward, our Rails upgrade process did not have to seem like an overwhelming endeavor.
00:22:44
These strategies, while simple, foster a supportive culture of constant improvement.
00:22:57
In relation to that other company, by adopting these strategies, they'll have significantly less to handle during their next upgrade.
00:23:10
Thanks to the recently applied strategies, they’ll approach upgrades with confidence.
00:23:19
They’ll experience lower resistance during the upgrade process thanks to the tried and tested methods.
00:23:30
Ultimately, these changes lead to not only a smoother upgrade process but also a more maintainable codebase.
00:23:39
The landscape of ongoing Rails upgrades will begin to feel more manageable, even when large updates roll out.
00:23:50
Instilling this mindset throughout the organization benefits both the collective growth of the code and the morale of the team.
00:24:03
As we evolve our strategies around Rails upgrades, the act of upgrading becomes less of a crisis and more of a continuous process.
00:24:10
I believe solidifying these principles will make our codebases cleaner and our development experience more enjoyable.
00:24:20
I encourage everyone to keep the conversation going around Rails upgrades—thank you!