Open Source Adventures: Episode 37: Fixing beeminder gem to work with Ruby 3

Here's an easy thing I needed to do. I was setting up a new laptop, with latest version of Ruby, and the script I used to snapshot beeminder data stopped working.

It turns out to be a trivial issue. beeminder gem depends on json 1.x, which doesn't work with Ruby 3. Simply changing that to json 2.x fixes it.

It also turns out this was already fixed, years ago, but the fix was never released. So all I did was poke them on GitHub issues.

Hotfixing gem dependencies

This is fine, but I have no patience for waiting for a new release.

So here's a hacky thing I did.

I installed latest version of json with gem install json.

Then I checked where beeminder gem is installed:

$ EDITOR=echo gem open beeminder
/Users/taw/.rbenv/versions/3.1.1/lib/ruby/gems/3.1.0/gems/beeminder-0.2.12

So my first idea was to hotfix /Users/taw/.rbenv/versions/3.1.1/lib/ruby/gems/3.1.0/gems/beeminder-0.2.12/beeminder.gemspec by replacing:

  gem.add_dependency 'json', '~> 1'

with:

  gem.add_dependency 'json', '~> 2'

That however did not work, as Rubygems is caching dependency information.

I needed to change /Users/taw/.rbenv/versions/3.1.1/lib/ruby/gems/3.1.0/specifications/beeminder-0.2.12.gemspec. Here's the final file:

# -*- encoding: utf-8 -*-
# stub: beeminder 0.2.12 ruby lib

Gem::Specification.new do |s|
  s.name = "beeminder".freeze
  s.version = "0.2.12"

  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
  s.require_paths = ["lib".freeze]
  s.authors = ["muflax".freeze, "bsoule".freeze]
  s.date = "2018-09-27"
  s.description = "Convenient access to Beeminder's API.".freeze
  s.email = ["support@beeminder.com".freeze]
  s.executables = ["beemind".freeze]
  s.files = ["bin/beemind".freeze]
  s.homepage = "https://github.com/beeminder/beeminder-gem".freeze
  s.rubygems_version = "3.3.7".freeze
  s.summary = "access Beeminder API".freeze

  s.installed_by_version = "3.3.7" if s.respond_to? :installed_by_version

  if s.respond_to? :specification_version then
    s.specification_version = 4
  end

  if s.respond_to? :add_runtime_dependency then
    s.add_runtime_dependency(%q<activesupport>.freeze, [">= 3.2", "< 6"])
    s.add_runtime_dependency(%q<chronic>.freeze, ["~> 0.7"])
    s.add_runtime_dependency(%q<json>.freeze, ["~> 2"])
    s.add_runtime_dependency(%q<highline>.freeze, ["~> 1.6"])
    s.add_runtime_dependency(%q<optimist>.freeze, ["~> 3"])
    s.add_runtime_dependency(%q<tzinfo>.freeze, ["~> 1.2"])
  else
    s.add_dependency(%q<activesupport>.freeze, [">= 3.2", "< 6"])
    s.add_dependency(%q<chronic>.freeze, ["~> 0.7"])
    s.add_dependency(%q<json>.freeze, ["~> 2"])
    s.add_dependency(%q<highline>.freeze, ["~> 1.6"])
    s.add_dependency(%q<optimist>.freeze, ["~> 3"])
    s.add_dependency(%q<tzinfo>.freeze, ["~> 1.2"])
  end
end

And that worked.

Should you do this?

To be honest, I don't really recommend doing this. Hotfixing globally installed dependencies is generally a poor idea, and there are more proper ways to do this.

You can also use this kind of hotfixing to quickly check if you're on a right track. If the gem was crashing with updated json, I'd know it's something bigger.

If you do so, you should uninstall and reinstall that gem afterwards to make sure no edits remain.

Coming next

For the next episode I'll go back to the Russian losses tracker, as there's a few more things I want to add.