Upgade to Ruby 3.1 breaks code when using CSV class from standard library

179 views Asked by At

I'm upgrading a Project written for JRuby 1.7 (corresponding on the language level to Ruby 1.9) to JRuby 9.4 (corresponding to Ruby 3.1.0). In this code, we have

require 'csv'

....
CSV.parse(string, csv_options) { .... }

where string is of class String and csv_optionsis of class Hash. This statement produces, when run under the new Ruby version, the error

 ArgumentError:
   wrong number of arguments (given 2, expected 1)

I found in the Ruby docs the following difference in the definition of parse:

Old version:

def parse(str, options={}, &block)

New version

def parse(str, **options, &block)

I understand that in the new Ruby, I would have to invoke parse as

CSV.parse(string, **csv_options) {....}

However, I would like to keep the code compatible for both versions, at least for some transition period, but the old JRuby does not understand **variable (I would get a syntax error, unexpected tPOW).

Is there a way to write the invocation of CSV.parse in such a way, that it preserves the original semantics and can run under Ruby 1.9 and Ruby 3.1? Currently the best solution for this problem which I can think of, is to write something like turning the block argument into a proc and writing

if RUBY_VERSION < '2'
  CSV.parse(string, csv_options, &myproc)
else  
  # Without the eval, the compiler would complain about
  # the ** when compiled with Ruby 1.9   
  eval "CSV.parse(string, **csv_options, &myproc)"
end

which looks pretty awful.

1

There are 1 answers

0
engineersmnky On

Not sure exactly what you are passing as csv_options but all versions can handle this using an a combination of implicit Hash/kwargs. e.g.

CSV.parse(string, col_sep: '|', write_headers: false, quote_empty: true) { ... }

If this is not an option then you going to need to patch the CSV class to make it backwards compatible e.g.

csv_shim.rb

# Shim CSV::parse to mimic old method signature 
# while supporting Ruby 3 kwargs argument passing
module CSVShim
  def parse(string, options={}, &block) 
    super(string, **options, &block)
  end 
end 

CSV.singleton_class.prepend(CSVShim)

Then you can modify as:

require 'csv'
require 'csv_shim.rb' if RUBY_VERSION > '2.6.0'

#... 
CSV.parse(string, csv_options) { .... }