Today I have released a new version of the chroma-db gem, version 0.3.0.
It was motivated due to an issue in the repository about Data not being available and the ask to relax the Ruby version requirement, which was 3.2.
When I wrote the library, I used the syntax from Ruby 3.2 so having it to work with earlier versions required a rewrite to make it compatible. I was not amused by having to rewrite parts of the gem, luckily I remembered seen Ruby Next a while ago from Evil Martians. So, I went to give it a try.
Ruby Next is a transpiler for the Ruby language. It can take a Ruby file using newer syntax and automatically generate one or more files that are compatible with earlier versions of Ruby. Since I was looking to be compatible down to Ruby 2.6, Ruby Next generated files for 2.6 and 3.1 version. These files cover all syntax changes in between Ruby versions.
Having the transpiled versions, Ruby Next takes care of loading the correct file depending on your Ruby version and replacing the original code using a Ruby refinement.
This sounded promising for my case with this gem. I focused on the section called “Integrating into a gem development” section of the README file.
First, the dependencies were added to the gemspec file of chroma-db.
if ENV["RELEASING_GEM"].nil? && File.directory?(File.join(__dir__, ".git")) spec.add_runtime_dependency "ruby-next", ">= 0.15.0" else spec.add_dependency "ruby-next-core", ">= 0.15.0" end spec.add_development_dependency "ruby-next", ">= 0.15.0"
Also, I make sure that generated files are packed into the gem but ignored by git.
spec.files = Dir.chdir(__dir__) do `git ls-files -z`.split("\x0").reject do |f| (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ notebook/ .git .circleci appveyor .standard.yml .rubocop.yml .solargraph.yml]) end + Dir.glob("lib/.rbnext/**/*")
Next, I changed the entry point
lib/chroma-db.rb of the gem to load Ruby Next and to set up the loading of transpiled files accordingly to the Ruby version at runtime.
require "ruby-next" require "ruby-next/language/setup" RubyNext::Language.setup_gem_load_path(transpile: true)
Note: This needs to happen before you require your gem files. Also, this gem does not work with
requiere_relative, so you need to use
Now to every file that uses the new Ruby’s syntax, I added the line to load the refinement.
At this point, the gem is almost ready to become compatible with earlier versions of Ruby, but first you have to generate the transpiled Ruby code. I opened the
Rakefile and added a task for this:
desc "Run Ruby Next nextify" task :nextify do sh "bundle exec ruby-next nextify -V" end
Also, I created a rc file called
.rbnextrc to configure Ruby Next.
nextify: | ./lib --min-version=2.6 --edge --proposed
Running the rake task
rake nextify generates the transpiled version of our code into
lib/.rbnext folder. It contains folders for different versions of Ruby and replicated inside the folder structure of our gem files that needs transpilation to become compatible. Don’t forget to add to your
.gitgnore file, the
The final changes are related to being able to run your test suite with different Rubies. Open the
spec_helper.rb and the following code just before the helper loads your gems files.
ENV["RUBY_NEXT_TRANSPILE_MODE"] = "rewrite" ENV["RUBY_NEXT_EDGE"] = "1" ENV["RUBY_NEXT_PROPOSED"] = "1" require "ruby-next/language/runtime" unless ENV["CI"]
Make sure that you can run your tests locally. To be 100% sure that your gem is now compatible with other Rubies, run your CI with a matrix of Ruby versions and be sure that
CI=true environment variable is set and also that the rake task to generate the transpiled code runs before your tests.
Overall, I’m happy that Ruby Next worked for me on this gem, this means that I can be used with earlier versions of Ruby for those that can’t use the latest version, but also I keep the joy of using newer syntax.
By the way, the issue was reported by the team working on langchainrb