Test your Ruby gem locally

How to resolve the pain of commit/push/pull to test how your gem fits in your project

Théo Carrive
Cheerz Engineering
3 min readJan 9, 2019

--

For many reasons that we won’t list here, at some point, in our life of Ruby developers, we have started to create some gems.

Gems are a great way to share code and to modularize our codebase while leveraging the power of Bundler. This is great.

Of course, our gem will have unit tests of its own.

However, while we develop it, we want to test it in our project to check if it integrates well. And very often, we want to do many iterations. And each time we do an iteration, right now, in addition to develop & test, we have to:

  • Commit the gem
  • Push the gem
  • Pull the gem from a microservice to test its usability

😰

This is a pain.

And we want to solve that pain.

Solution #1

Quick and dirty.

In the microservice Gemfile, we replace the git URL with a local path:

./microservice/Gemfile:# TODO: Change me before commiting anything!!
gem 'cheerz_on_rails', path: '../cheerz_on_rails'

Ok, this works well, and allow us to make changes to our gem and directly see the change in our microservice.

However, this is what we risk to see very soon when a giddy developer will forget to check the work he commits:

Manual checks to do before commit = mental workload = errors

Solution #2

Let’s try to make something a bit cleaner.

What we want is to have a special way of using our microservice, so that it loads the gem from local instead of fetching it from SSH.

We cannot use a condition in the Gemfile, based on the environment, otherwise, it would modify the Gemfile.lock.

If we look at the config/boot.rb of any Rails project, we see that we could pass a Gemfile path, thanks to an environment variable:

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])

This is the technique that we are going to use.

First, we create this new Gemfile, that we will call Gemfile-dev, and for each rails command that we are going to launch, we are going to prefix it with BUNDLE_GEMFILE=Gemfile-dev.

Example:

BUNDLE_GEMFILE=Gemfile-dev bundle exec rails server

Now, what we want, is to have a variable that will be overridden in the Gemfile-dev, to specify that we take the gem from local.

First, we modify the regular Gemfile to take an argument from a variable:

./microservice/Gemfile:(...)@cheerz_on_rails_params ||= {
git: 'git@github.com:cheerz/cheerz-on-rails.git',
branch: :master
}
gem 'cheerz_on_rails', @cheerz_on_rails_params
(...)

And now, we just have to setup our Gemfile-dev file:

./microservice/Gemfile-dev:@cheerz_on_rails_params = {path: '../cheerz_on_rails'}eval_gemfile "./Gemfile"

Our Gemfile-dev will just override the @cheerz_on_rails_params variable and then, eval the regular Gemfile.

Great. If we bundle install with the following command, it works:

BUNDLE_GEMFILE=Gemfile-dev bundle install

However, all the versions in your Gemfile-dev.lock will be different from the one present in the regular Gemfile.lock, because bundler won’t have read the Gemfile.lock in the first place. Thus, we modify our Gemfile-dev file to perform a last trick:

./microservice/Gemfile:# Copy to ensure same version for gems
FileUtils.cp("Gemfile.lock", "Gemfile-dev.lock")
# Override the variable that will be used in the Gemfile
@cheerz_on_rails_params = {path: '../cheerz_on_rails'}
# Read the regular Gemfile
eval_gemfile "./Gemfile"

Et voilà! Everything works and is safe to be committed!

🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉

Thanks to Maxime Garcia with whom I have made this work!

Do you want to see more than what we’ve shown here?

Don’t hesitate to contact us, it will be a pleasure to talk, or even to welcome for you for a coffee at our office (France -> Paris -> Place de Clichy)!

Just so you know, we are hiring engineers! Don’t hesitate to contact us, or check our page here!

--

--