Today I was working on some code that generated some CSV reports like this:

  def self.my_cool_report(options = {})
CSV.generate(options) do |csv|

The codebase was recently updated from Ruby 2.7.6 to Ruby 3.2.1. The reports were failing to generate with a weird error - "no implicit conversion of Hash into String".

I set bindings inside the generate method, because I was in the process of changing some other things, and figured the error must be related. But my binding was never hit.

I started digging into the code for CSV.generate. The full method looks like this, as of Ruby 3.2:

def generate(str=nil, **options)
encoding = options[:encoding]
# add a default empty String, if none was given
if str
str =, IO::SEEK_END)
str.set_encoding(encoding) if encoding
str = +""
str.force_encoding(encoding) if encoding
csv = new(str, **options) # wrap
yield csv # yield for appending
csv.string # return final String

What I found is that the generate method is treating what the original author of my team's code thought they were passing in as options as the string for the CSV to start writing to. It's calling on that empty options hash we gave it.

I looked back at the 2.7 version of the CSV library - and it too called in the same way. That made me curious if StringIO had changed.

I did a quick experiment, switching the version of Ruby I used and calling with an empty hash. Lo and behold, the results were very different!

>   rbenv local 2.7.6
> irb
irb(main):001:0> require 'stringio'
=> true
=> #<StringIO:0x00007fa0fa185a68>
irb(main):003:0> exit
> rbenv local 3.2.1
> irb
irb(main):001:0> require 'stringio'
=> true
(irb):2:in `initialize': no implicit conversion of Hash into String (TypeError) from (irb):2:in `new' from (irb):2:in `<main>' from /.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>' from /.rbenv/versions/3.2.1/bin/irb:25:in `load' from /.rbenv/versions/3.2.1/bin/irb:25:in `<main>'

In Ruby 2.7.6, was happy to be instantiated with an empty hash, while in Ruby 3.2.1, threw an error if you tried to instantiate it that way -- that no implicit conversion error I was seeing when trying to pass an empty hash to CSV.generate.

I updated all the report methods to default options to nil if no options were provided instead of an empty hash, and now my code works again.