Colaborando con Ruby

by Gastón Ramos

Bueno en este post voy a escribir un ayuda memoria (para mí, no para uds. obvio) de cómo enviar un Patch al core Ruby. Existe un documento que explica los pasos generales para enviar un patch al core de ruby que es el “Improving Ruby, Patch by Patch” lo pueden encontrar acá:
Improving Ruby, Patch by Patch

Y después hay un mail que escribió Matz que es una lista de cosas a tener en cuenta para facilitarle a los devs las cosas y que el patch se pueda aplicar fácilmente:

Patch writer’s guide to submit

Bueno al grano, todo comenzó así: Ayer estaba leyendo la lista de tickets de rails y me encontré con esto:

https://rails.lighthouseapp.com/projects/8994/tickets/3475-activesupportmultibytecharsgsub-fails-while-stringgsub-works

todo parece indicar que es un bug en activesupport como indica el ticket, pero con un poquito de investigación podemos ver que esto no es así, el problema real está en la lib CGI que viene con la lib Standard de ruby, particularmente el método CGI::escape, veamos:
El archivo en cuestión es ruby_1_8/lib/cgi.rb :

  # URL-encode a string.
  #   url_encoded_string = CGI::escape("'Stop!' said Fred")
  #      # => "%27Stop%21%27+said+Fred"
  def CGI::escape(string)
    string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
      '%' + $1.unpack('H2' * $1.size).join('%').upcase
    end.tr(' ', '+')
  end

El error lo tira exactamente cuando intenta hacer $1.size y $1 es nil :

NoMethodError: undefined method `size' for nil:NilClass
  from /usr/lib/ruby/1.8/cgi.rb:343:in `escape'

Buscando un poco en google encontré que las variables con dolar no funcionan bien en ciertas situaciones
cuando se usan con bloques, acá está la prueba y la explicación:

http://www.justskins.com/forums/bug-when-rerouting-string-gsub-with-a-block-using-1-a-52852.html

Explicado esto, ahora hay que resolver el problema, entonces la idea es reemplazar las variables $1 por un argumento en el bloque del código (al menos esa fué mi idea), pienso en un test que pueda
reproducir el problema, mmmmm…. dónde creo que archivo de tests para CGI? (dado que no existe ninguno al día de hoy, recuerdo que estoy ruby_1_8), listo creo que tests/cgi/test_cgi.rb es un buen lugar y lo primero que se me ocurre es esto:

require "cgi"
require "test/unit"

class String
  alias : old_gsub :gsub
  def gsub(*args, &block)
    old_gsub(*args, &block)
  end
end

class TestCGI < Test::Unit::TestCase

 def test_escape_should_work_inside_a_block
     original_string  = "An Interview with Criteri…"
     escaped_string   = "An+Interview+with+Criteri%26%238230%3B"
     assert escaped_string, CGI.escape(original_string)
  end

end

Corro el test:

  make test-all TESTS='-v cgi/test_cgi.rb'

Y el resultado es:

1) Error:
test_escape_should_work_inside_a_block(TestCGI):
NoMethodError: undefined method `size' for nil:NilClass
    /home/gramos/srcs/ruby_1_8/lib/old_cgi.rb:343:in `escape'
    /home/gramos/srcs/ruby_1_8/test/cgi/test_cgi.rb:9:in `old_gsub'
    /home/gramos/srcs/ruby_1_8/test/cgi/test_cgi.rb:9:in `gsub'
    /home/gramos/srcs/ruby_1_8/lib/old_cgi.rb:342:in `escape'
    /home/gramos/srcs/ruby_1_8/test/cgi/test_cgi.rb:25:in `test_escape_should_work_inside_a_block'

Genial, el problema está representado en el test, ahora el Fix:

  # URL-encode a string.
  #   url_encoded_string = CGI::escape("'Stop!' said Fred")
  #      # => "%27Stop%21%27+said+Fred"
  def CGI::escape(string)
    string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do |s|
      '%' + s.unpack('H2' * s.size).join('%').upcase
    end.tr(' ', '+')
  end

Sencillo. Bueno después de esto hice lo mismo para CGI::unescape, con el test y todo. Luego de chequear que todo esté Ok, creé el patch de esta forma:

svn diff > cgi_escape_work_with_blocks_and_avoid_dollars_variables.patch

Y después envié un mail a la lista de ruby-core adjuntando el patch y con una explicación del problema.

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/27198

Ahora hay que esperar para ver si lo aplican…