Search code examples
rubythor

Why is opening String class inside thor file not working?


I know I can open a class like String and add functionality. This test script camelize.rb works pretty well.

#!/usr/bin/env ruby
class String
  def camelize
    self.split("_").map(&:capitalize).join
  end
end

class Test
  def test
    p "test_me".camelize
  end
end

Test.test

Prints "TestMe"

However inside a thor file this doesn't work. E.g. test.thor

p "TEST ONE"
class String
  p "TEST TWO"
  def camelize
    self.split("_").map(&:capitalize).join
  end
end

class Test < Thor
  p "TEST THREE"
  desc "camel", "A test"
  def camel
    p "test_me".camelize
  end
end

Installing it via thor install test.thor, running

$ thor test:camel
"TEST ONE"
"TEST TWO"
"TEST THREE"
/Users/Kassi/.thor/ba3ea78d7f807c4c13ec6b61286788b5:13:in `camel': undefined method `camelize' for "test_me":String (NoMethodError)

Why and how to fix it?


Solution

  • Why?

    The problem is here:

    Thor::Sandbox.class_eval(content, path)
    

    So what it does is take your file and load it inside of an empty module, thus namespacing it (not sure if qualifies as "sandboxing").

    class Thor
      module Sandbox
      end
    end
    

    So, your attempt at reopening the String actually creates a new class Thor::Sandbox::String which no one knows about. String literals continue to create instances of String.

    How to fix?

    Open the top-level string instead of creating a nested one.

    class ::String
      def camelize
        self.split("_").map(&:capitalize).join
      end
    end
    

    Bonus content

    Thor actually already includes method for camelizing strings, Thor::Util.camel_case:

      def camel_case(str)
        return str if str !~ /_/ && str =~ /[A-Z]+.*/
        str.split("_").map { |i| i.capitalize }.join
      end