Search code examples
ruby-on-railsrubyruby-on-rails-3.2ruby-2.3

Ruby 2.3.3: Weird Tempfile.new([name, prefix]) basename converted to hash


Testing an upgrade to Ruby 2.3.3 for our Rails 3.2.22.2 application, and getting a weird situation where we are passing an array as the first argument to Tempfile.new, but it's ending up as a hash.

I've patched tempfile.rb to output the basename argument being passed in.

In an irb session (non-Rails), everything is fine:

> require 'tempfile'
true
> Tempfile.new(['test', '.csv'])
["home", ".csv"] # output of basename argument for Tempfile.new
 => #<Tempfile:/var/blah/test###.csv> 

In a rails console session:

> Tempfile.new(['test', '.csv'])
{"test"=>nil, ".csv"=>nil}
ArgumentError: unexpected prefix: {"test"=>nil, ".csv"=>nil}
from /path/to/ruby-2.3.3/lib/ruby/2.3.0/tmpdir.rb:113:in `make_tmpname'

Gotta be a gem or something, but can't figure out for the life of me why this is happening or where or what is changing the behavior.

Any ideas or suggestions on how to debug?


Solution

  • In your case I think that somewhere in your code you have the Array#to_hash method defined.

    I had the same issue and for some reason when a method has a default param, in this case basename="", and a double splatted parameter, Ruby calls the to_hash function on the first param.

    See the following example:

    class Dummy
      def initialize(val = "", **options)
        puts "val = #{val}"
        # puts "Options: #{options}"
      end
    end
    
    class Array
      def to_hash
        puts "to_hash called on #{self}"
      end
    end
    
    Dummy.new(["Joe", "Bloe"])
    

    This will output

    to_hash called on ["Joe", "Bloe"]
    val = ["Joe", "Bloe"]
    

    But when there's no default value for the val param, you'll get:

    val = ["Joe", "Bloe"]
    

    Note that the TempFile#initialize function signature was changed from Ruby 2.1 to Ruby 2.2.

    Here's the diff:

    -  def initialize(basename, *rest)
    +  def initialize(basename="", tmpdir=nil, mode: 0, **options)
    

    Notice that basename doesn't have a default value anymore.