Search code examples
rubyoverridingputs

override namespaced puts only works after overriding Kernel.puts?


Sorry for the vague question title, but I have no clue what causes the following:

module Capistrano
  class Configuration
    def puts string
      ::Kernel.puts 'test'
    end
  end
end

Now when Capistrano calls puts, I don't see "test", but I see the original output.

However, when I also add this:

module Kernel
  def puts string
    ::Kernel.puts 'what gives?'
  end
end

Now, suddenly, puts actually returns "test", not "what gives?", not the original content, but "test".

Is there a reasonable explanation why this is happening (besides my limited understanding of the inner-workings of Ruby Kernel)?

Things that look off to me (but somehow "seem to work"):

  • I would expect the first block to return 'test', but it didn't
  • I would expect the combination of the two blocks to return 'what gives?', but it returns 'test'?
  • The way I override the Kernel.puts seems like a never-ending loop to me?

Solution

  • module Capistrano
      class Configuration
        def puts string
          ::Kernel.puts 'test'
        end
        def an_thing
          puts "foo"
        end
      end
    end
    
    Capistrano::Configuration.new.an_thing
    

    gives the output:

    test
    

    The second version also gives the same output. The reason is that you're defining an instance level method rather than a class level method (this post seems to do a good job explaining the differences). A slightly different version:

    module Kernel
      def self.puts string
        ::Kernel.puts 'what gives?'
      end
    end
    

    does the following. Because it is causing infinite recursion, like you expected.

    /tmp/foo.rb:14:in `puts': stack level too deep (SystemStackError)
        from /tmp/foo.rb:14:in `puts'
        from /tmp/foo.rb:4:in `puts'
        from /tmp/foo.rb:7:in `an_thing'
        from /tmp/foo.rb:18
    
    shell returned 1