Rails Metal + Thin = no ‘each’ for String?

Posted: 17th January 2010 by M. E. Patterson in Coding
Tags: , ,

Just solved an interesting problem when using the latest Thin web server (1.2.5) with Ruby 1.9.1 and Rails Metal.

The code in Rails Metal endpoint:

  def call(env)
    if env["PATH_INFO"] =~ /^\/tags.txt/
      request = Rack::Request.new(env)
      params = request.params
      query = params['q']
      [200, { "Content-Type" => "text/plain" }, words_from(query).join("\n")]
    else
      [404, { "Content-Type" => "text/html" }, "Not Found"]
    end
  end

Don’t worry about what words_from(query) actually does. It just returns an array of words. The deal is, I was trying to output a list of tags for use with a javascript autocompleter — one tag per line in a plain/text file for speed. Sure, sure, I could use JSON or XML or whatever, but I didn’t, because it was easier to get the javascript thing working quickly this way and I’m lazy.

Under ruby 1.8.7 with thin or webrick, this works fine. Under ruby 1.9.1 with Webrick (ugh!), this works fine. Under ruby 1.9.1, with Thin, this bombs with:

!! Unexpected error while processing request: undefined method `each’ for #<String:0x000001037abd08>

After two hours of debugging, here’s the simple fix — pass an array with one string element to the return line instead of a pure string object:

[200, { "Content-Type" => "text/plain" }, [words_from(query).join("\n")]]

Seriously? Yes. Seriously.

This makes Thin happy and prevents it from trying to enumerate a String, which doesn’t work in Ruby 1.9.

I haven’t gone back yet to see if this borks the other web servers, but I’m using Thin on this one anyway, so at the moment I don’t really care. As always, your mileage may vary.

  1. Joshaven says:

    Thank you. I was having a similar problem and because of you didn’t have to debug for two hours.

    I was getting a Time object rather then a string… with Sinatra + Thin

  2. Lory Coloma says:

    -You can’t really put it any more straightforward than that.

  3. [...] This post was mentioned on Twitter by Nicholas, Matt E. Patterson. Matt E. Patterson said: RT @mepatterson Rails Metal + Thin = no ‘each’ for String? « me, Patterson http://tinyurl.com/ybeaxgq [...]

  4. ncancelliere says:

    In Ruby 1.9 String is no longer an enumerable using each(). You can check out this blog post which goes into details about the changes: http://blog.grayproductions.net/articles/ruby_19s_string

    This is probably one of the bigger changes done in Ruby 1.9 … and I’m guessing Webrick is doing something to work around this, but maybe Thin hasn’t updated to 1.9 yet??

    • M. E. Patterson says:

      Yeah, that was what I thought at first, except I dug into the Thin code and discovered that they have a line:

      if @body.is_a?(String)

      and then they just yield the whole string instead of trying to break it into chunks. The other half of the if-else seems to expect that the @body is something that is actually enumerable and yields that back in chunks.

      What I discovered (maybe something new in ActionController 2.3.5?) is that this ActionController BodyWrapper thing gets wrapped around my result, which, because it’s not a String object, makes it fall through to the else and kaboom. So it’s clear that the Thin guys had attempted to account for 1.9, but I guess something new in ActionController invalidated their fix. But by turning my plain string into an Array, it made the enumerating part of the else statement enumerate the single element and then it yields it perfectly. Was easier/more pleasant to make a tiny fix to my code than to hack up Thin.