Resolving Compatibility Issues with C Extensions When Upgrading Ruby
“For want of a nail the shoe was lost, For want of a shoe the horse was lost, For want of a horse the rider was lost, For want of a rider the battle was lost, For want of a battle the kingdom was lost, And all for the want of a horseshoe nail.”
This proverb – often attributed to Benjamin Franklin, though it is likely much older – is a great illustration of the problems that can occur when we are dealing with complex systems. And, of course, a modern web application written in Ruby is a very complex system indeed. Web applications often have many dependencies, and those dependencies in turn have their own dependencies. For this reason, it is not uncommon to find that an application upgrade can be blocked by a single, solitary gem which is failing to build.
In this article, we’ll discuss what to do about this particular scenario; specifically, we will talk about what to do when a gem with a C extension fails to build. Of course, this is not a general guide to upgrading Ruby applications, but it is a guide to one of the most common problems that can occur when upgrading a Ruby application.
First, Some Background
Before we get into that, though, let’s take a step back and talk about Ruby itself: decisions and ideas that are central to the idioms of Ruby programming are also central to understanding why RubyGems and Bundler have made the systemic decisions that they have.
First, Ruby is a dynamically typed language. This means that you don’t have to declare the type of a variable before you use it. This is in contrast to a statically typed language like C, where you have to declare the type of a variable before you use it. This is a trade-off: dynamically typed languages are often easier to write, but statically typed languages are often easier to debug.
This has a number of implications for how Ruby is written. For example, it’s not strictly typed, but rather uses “ducktyping.” This means that if an object walks like a duck and talks like a duck, it’s a duck. In other words, if an object has the methods that you expect it to have, you can use it as if it were of the type that you expect it to be.
This is a very powerful feature, but it has a downside: it can be hard to know what methods an object has. This is where documentation comes in handy. It’s also where a tool like ri comes in handy. ri is a command-line tool that can be used to look up documentation for Ruby classes and methods.
This brings us to our next point: Ruby is a very flexible language. It’s possible to change the behavior of existing classes and methods. This is called “monkey patching,” and it’s a very powerful feature. However, it’s also a very dangerous feature. It can be hard to know what the effect of a monkey patch will be.
Finally, Ruby is a very expressive language. It’s possible to write code that is very concise and easy to read. This is one of the reasons why Ruby is so popular.
So, what does all of this have to do with C extensions? Well, it turns out that C extensions are a way of getting around some of the limitations of Ruby. For example, if you have a performance-critical piece of code, you can write it in C and then call it from Ruby. This can be a great way to improve the performance of your application.
However, it also has a downside: it can be hard to know what the effect of a C extension will be. This is because C extensions are written in C, not Ruby. This means that they don’t have the same safety guarantees that Ruby code does. For example, it’s possible for a C extension to crash your application.
It also means that C extensions are not as portable as Ruby code. A C extension that works on one platform may not work on another. This is because C extensions are compiled, and the compilation process is platform-specific.
Finally, it means that C extensions are not as easy to debug as Ruby code. If a C extension crashes your application, it can be hard to know why.
So, with all of that in mind, let’s talk about what to do when a C extension fails to build.
An Ounce of Prevention is Worth a Pound of Cure
When you’re upgrading a Ruby application, the first thing you should do is to make sure that you have a good test suite. This is important for a number of reasons. First, it will help you to verify that your application still works after the upgrade. Second, it will help you to identify any regressions that may have been introduced by the upgrade. Finally, it will help you to identify any gems that are no longer compatible with the new version of Ruby.
If you don’t have a good test suite, now is a good time to write one. It doesn’t have to be perfect, but it should cover the critical parts of your application.
Once you have a good test suite, you should run it before you start the upgrade. This will help you to identify any existing problems with your application. It will also help you to get a baseline for the performance of your application.
Next, you should create a new branch in your version control system. This will allow you to experiment with the upgrade without affecting your main line of development.
Once you have a new branch, you should upgrade your Ruby version. If you’re using a tool like rbenv or rvm, this is as simple as running a single command. For example, if you’re using rbenv, you can run the following command:
rbenv install 3.0.0
rbenv local 3.0.0
This will install Ruby 3.0.0 and set it as the local Ruby version for your project.
Once you have the new version of Ruby installed, you should try to bundle your application. You can do this by running the following command:
bundle install
If this command succeeds, you should run your test suite. If your test suite passes, you should be in good shape. However, it’s more likely that one of these two commands will fail. If bundle install fails, it’s likely that one of your gems is not compatible with the new version of Ruby. If your test suite fails, it’s likely that one of your gems is not compatible with the new version of Ruby, or that your application code is not compatible with the new version of Ruby.
In either case, the next step is to identify the problematic gem.
Troubleshooting
The first thing you should do is to look at the error message. If bundle install failed, the error message should tell you which gem failed to install. If your test suite failed, the error message should tell you which gem is causing the problem.
If the error message is not helpful, you can try to bisect your Gemfile. This is a process of elimination that will help you to identify the problematic gem. To do this, you should comment out half of the gems in your Gemfile and then try to bundle your application. If it succeeds, you know that the problematic gem is in the half that you commented out. If it fails, you know that the problematic gem is in the half that you didn’t comment out. You can repeat this process until you’ve identified the problematic gem.
Once you’ve identified the problematic gem, you should try to update it. You can do this by running the following command:
bundle update <gem_name>
If this command succeeds, you should run your test suite again. If your test suite passes, you should be in good shape. However, it’s more likely that this command will fail. If it fails, it’s likely that the gem is not compatible with the new version of Ruby.
In this case, you should visit the gem’s documentation or GitHub repository for compatibility notes. You should also search for issues or pull requests related to the Ruby version you’re upgrading to.1
If you can’t find any information about the gem’s compatibility, you can try to recompile the extension. Sometimes, the gem’s precompiled binary isn’t compatible with your new Ruby version or system architecture. To recompile:
gem uninstall <gem_name>
gem install <gem_name>
This process will attempt to build the C extension from source. Ensure you have the necessary development tools and libraries installed (e.g., gcc, make, and Ruby development headers).2
If you’re still having problems, you can try to install the gem with a specific version. For example, if you’re trying to install version 1.2.3 of the nokogiri gem, you can run the following command:
gem install nokogiri -v 1.2.3
If this command succeeds, you should be in good shape. However, it’s more likely that this command will fail. If it fails, it’s likely that the gem is not compatible with the new version of Ruby.
In this case, you have a few options.
Option 1: Find an Alternative
The first option is to find an alternative to the gem. If the gem is not critical to your application, this is probably the best option. You can search for alternatives on RubyGems.org.
If you can’t find an alternative, you can try to write one yourself. This is a lot of work, but it’s a good way to learn more about Ruby.
Option 2: Patch the Gem
If the gem is critical to your application, you may need to patch it yourself. This is a lot of work, but it’s a good way to learn more about C extensions.
To do this, you’ll need to download the source code for the gem. You can do this by running the following command:
gem fetch <gem_name> -v <version>
gem unpack <gem_name>-<version>.gem
This will download the gem and unpack it into a new directory.
Once you have the source code, you’ll need to find the C extension. It’s usually located in a directory called ext.
Once you’ve found the C extension, you’ll need to modify it to work with the new version of Ruby. This is the hard part. You’ll need to be familiar with C and with the Ruby C API.
Once you’ve modified the C extension, you’ll need to compile it. You can do this by running the following command:
cd <gem_name>-<version>
rake compile
If this command succeeds, you should have a compiled C extension. You can then use this C extension in your application.
Option 3: Contact the Maintainer
If you’re not comfortable patching the gem yourself, you can try to contact the maintainer. You can usually find the maintainer’s contact information on the gem’s page on RubyGems.org.
When you contact the maintainer, you should be as specific as possible. You should tell them what version of Ruby you’re using, what version of the gem you’re using, and what error you’re seeing. You should also tell them what you’ve tried so far.
If you’re lucky, the maintainer will be able to help you. However, it’s also possible that the maintainer will not be able to help you. In this case, you’re back to one of the other two options.
A Final Word
Upgrading a Ruby application can be a lot of work. However, it’s also a great way to learn more about Ruby. If you’re having problems, don’t be afraid to ask for help. There are a lot of people in the Ruby community who are willing to help. You can find them on the Ruby mailing list, on Stack Overflow, and on the Ruby subreddit.
Footnotes
-
SparkleMotion, “Bundler platform errors upgrading alpine image to 1.18.3,” GitHub Issue #3440, sparklemotion/nokogiri, 2025, https://github.com/sparklemotion/nokogiri/issues/3440. ↩
-
SparkleMotion, “Installing Nokogiri,” Nokogiri Official Documentation, 2026, https://nokogiri.org/tutorials/installing_nokogiri.html. ↩
You May Also Like
Fixing Catastrophic Backtracking in Custom Ruby Regexes
A guide to understanding and fixing catastrophic backtracking in Ruby's regular expressions to prevent performance issues and application crashes.
Form Handling in Modern Rails: Replacing Rails UJS with Inertia.js useForm
Learn how to replace legacy Rails UJS form submissions with Inertia.js useForm for modern form handling in Rails applications.
HIPAA Compliance Risks of Running End-of-Life (EOL) Rails Versions
Explore the security vulnerabilities and HIPAA compliance risks of running end-of-life Ruby on Rails in healthcare apps handling ePHI, and how to fix them.