* Me levanté: 6:30hs
* El Chelo llego a casa: 7:00hs
* Agua: 8:00hs
* Destino: 11:10hs
* Llegamos de vuelta a casa: 12:30hs
* Cantidad de horas remadas: 4hs aprox.

Bueno, nos retrasamos un poquito por que tuve que pegarle una goma Eva al portaequipaje de mi auto para que no se raye el kayak, en el camino 1km antes de llegar al puente del Leyes me crucé a un viejo amigo del barrio, Jorge, conversamos con él unos 15 minutos y seguimos hasta el puente, paramos unos 20 mintos sacamos unas fotos y emprendimos la vuelta, había bastante viento, al llegar la Fryda estaba esperándome, como siempre.

This slideshow requires JavaScript.

Mi nuevo kayak aloha loa II

Tuesday, 13 December , 2011

Hace unos días me compré este hermoso bote de plasticos tigre  luego de vender mi anterior bote que fué el primero que tuve, un k1 de antlanti kayaks, este kayak tiene la particularidad de que es sit on top es decir que el palista va sentado arriba del bote a diferencia del Aloha. Al navegar el Aloha siento que voy remando de una forma mucho más relajada (obviamente el aloha es mucho más rápido que el k1) se siente a apenas inestable, yo estaba acostumbrado al k1 que es muy estable, la dirección  del kayak es muy buena (todavía no lo he probado con mucho viento) y se lo puede maniobrar bastante bien escorándolo para doblar por ej. La primer vez que salí tuve la mala idea de regular el apoya pies, logré desajustar los tornillos mariposa pero al querer volver a ajustarlos en la nueva posición, me resultó *muy* difícil estuve como 30 minutos tratando de hacerlo hasta que lo logré, así que mi recomendación es que regulen el apoya pies con tiempo y paciencia (antes de salir a remar). Las terminaciones del bote son bastante buenas aunque tiene algunos detalles, en la proa tiene como una pequeña cachadura que pareciera reparada, la parte interior del cockpit debería estar mas lisa o más lijada para mi gusto (es un poco áspera). Con respecto a los tambuchos trae 2 bastante amplios, una de las veces que salí a remar me dí vueltas a propósito y en el tambucho trasero me entró más de medio litro de agua, el delantero estaba seco.

En conclusión estoy muy contento con mi bote nuevo es lindo, rápido y cómodo, y las únicas desventajas que el econtré por ahora son la incomodidad para regular el apoya pies (creo que esto debe pasar en la mayoría de los kayaks como este, perdón por mi ignoracia) y el tema del agua en el tambucho que debería ser 100% estanco, ahora estoy contactando al lugar dónde lo compré  (http://www.saffari-kayaks.es.tl/) para ver como podemos solucionar el tema del tambucho,  A propósito, el bote me salió $3800 pesos argentinos.

Quiero dejar en claro que lo que escribí son mis opiniones personales que soy un novato en lo que es el kayakismo, comencé a remar cuando me compré mi primer bote hace un poco más de 2 años aproximadamente, así que tomen mis opiniones como lo que son, reflexiones de un kayakista inexperto, :).

¿Cómo funciona RubyGems?

Tuesday, 22 February , 2011

Desde hace un tiempo que estoy interesado en conocer un poco más en detalle como funciona rubygems, así que hoy decidí comenzar a aprender más sobre rubygems y cómo las gemas son cargadas en memoria.

Bueno, comencemos:
Entro al intérprete interactivo de ruby e intento requerir la bioblioteca activemodel:


ruby-1.9.2-p136 :001 > Gem.loaded_specs
 => {}
ruby-1.9.2-p136 :002 > require 'active_model'
 => true
ruby-1.9.2-p136 :003 > Gem.loaded_specs
 => {"activemodel"=>#<Gem::Specification name=activemodel version=3.0.4>,
"activesupport"=>#<Gem::Specification name=activesupport version=3.0.4>,
"builder"=>#<Gem::Specification name=builder version=2.1.2>,
"i18n"=>#<Gem::Specification name=i18n version=0.5.0>}

Cómo podemos observar aparecen varias gemas nuevas cargadas, active_model y sus dependencias, pero ¿cómo es que esto se lleva cabo?

Bien si nos fijamos en /lib/rubygems.rb en el repo de rubygems al final de todo hace esto:


require 'rubygems/custom_require' unless Gem::GEM_PRELUDE_SUCKAGE

y si vamos al archivo lib/rubygems/custom_require.rb vemos que lo hace es redefinir el método require de Kernel, y activa la gema de esta forma:


Gem.try_activate(path)

podemos probar esto directamente en el intérprete:


ruby-1.9.2-p136 > Gem.try_activate 'active_resource'
=> true
ruby-1.9.2-p136 :007 > Gem.loaded_specs['activeresource']
 => #<Gem::Specification name=activeresource version=3.0.4> 


Entonces vamos de nuevo al archivo rubygems.rb y miramos como es que funciona Gem.try_activate:

Lo primero que hace es buscar la gema que estamos intentando requerir, y lo hace así:


 spec = Gem.searcher.find path

Gem.searcher es una instancia de Gem::GemPathSearcher entre otras cosas tiene la lista de todas las gemspecs instaldas para después poder hacér búsqueda con Gem::GemPathSearcher#find. Entonces siguiendo con Gem.try_activate, si encuentra alguna coincidencia ejecuta

Gem.activate spec.name, "= #{spec.version}"

como podemos ver saca la versión de la spec encontrada (que es la más alta instalada), entonces ahora vamos a ver

Gem.activate

Éste método retorna true si la gema fué activada, false si ya está cargada o una excepción en cualquier otro caso (no se pudo cargar por algún motivo). La manera en que activa la gema es agregando el path de la misma al $LOAD_PATH de ruby.


 def self.activate(gem, *requirements)
   if requirements.last.is_a?(Hash)
      options = requirements.pop
   else
      options = {}
   end

   # Esta línea parsea los requerimientos que vienen como argumento
   # y si no viene ninguno pone los defaults, los requerimientos no son
   # otra cosa que restricciones de la versión de la gema,
   # el requerimiento por defecto es >= 0

   requirements = Gem::Requirement.default if requirements.empty?

   # Básicamente Gem::Dependency es un contenedor de
   # Gem name, requirement

   dep = Gem::Dependency.new(gem, requirements)

   sources = options[:sources] || []

   # creo que esta línea require especial antención
   # (veremos los detalles más adelante) pero por ahora nos
   # alcanza con saber que Gem._unresolved crea una lista
   # de objetos Gem::DependencyList (que primero está vacía)
   # de dependecias sin resolver

   matches = _unresolved.find_all { |spec| spec.satisfies_requirement? dep }

   if matches.empty? then
      matches = Gem.source_index.find_name(dep.name, dep.requirement)

      # Si no ecuentra la gema buscando con el nombre y los
      # requerimientos reporta el error
      report_activate_error(dep) if matches.empty?

      # Si la gema ya está cargada (cualquier versión)
      if @loaded_specs[dep.name] then
        # This gem is already loaded.  If the currently loaded gem is not in the
        # list of candidate gems, then we have a version conflict.
        existing_spec = @loaded_specs[dep.name]

        # Pregunta si la gema que está cargada es la misma versión
        # que la  quiero activar
        unless matches.any? { |spec| spec.version == existing_spec.version } then
          sources_message = sources.map { |spec| spec.full_name }
          stack_message = @loaded_stacks[dep.name].map { |spec| spec.full_name }

          msg = "can't activate #{dep} for #{sources_message.inspect}, "
          msg << "already activated #{existing_spec.full_name} for "
          msg << "#{stack_message.inspect}"

          e = Gem::LoadError.new msg
          e.name = dep.name
          e.requirement = dep.requirement

          raise e
        end

        # La gema que quiero cargar ya está cargada
        return false
      end
    end

    # La gema no está cargada entonces activa la última versión
    spec = matches.last

    conf = spec.conflicts
    unless conf.empty? then
      why = conf.map { |k,v| "#{k} depends on #{v.join(", ")}" }.join ", "

      raise LoadError, "Unable to activate #{spec.full_name}, but #{why}"
    end

    return false if spec.loaded?

    spec.loaded = true
    @loaded_specs[spec.name]  = spec
    @loaded_stacks[spec.name] = sources.dup

    spec.runtime_dependencies.each do |spec_dep|
      next if Gem.loaded_specs.include? spec_dep.name
      specs = Gem.source_index.search spec_dep, true

      _unresolved.add(*specs)
    end

    current = Hash.new { |h, name| h[name] = Gem::Dependency.new name }
    Gem.loaded_specs.each do |n,s|
      s.runtime_dependencies.each do |s_dep|
        current[s_dep.name] = current[s_dep.name].merge s_dep
      end
    end

    _unresolved.remove_specs_unsatisfied_by current

    require_paths = spec.require_paths.map do |path|
      File.join spec.full_gem_path, path
    end

    # gem directories must come after -I and ENV['RUBYLIB']
    insert_index = load_path_insert_index

    if insert_index then
      # gem directories must come after -I and ENV['RUBYLIB']
      $LOAD_PATH.insert(insert_index, *require_paths)
    else
      # we are probably testing in core, -I and RUBYLIB don't apply
      $LOAD_PATH.unshift(*require_paths)
    end

    return true
  end


Si alguien encuentra algún en error en mi seguimiento hasta acá le pido que haga un comentario en este post, Continuará…

Acts as Rubylit 2010

Wednesday, 03 November , 2010

Este post es para invitarte a las jornadas de este año a realizarse en
la Facultad de Ing. y Ciencias Hídricas en Santa Fe,
en dónde vamos a tener charlas muy interesantes tanto para principiantes
como para los que están en niveles más avanzados, con oradores de reconocimiento
nacional e internacional.
Una vez más el evento es gratuito, pero es necesario que estés
registrado para poder participar del mismo, es por esto que te
invitamos a hacerlo desde acá:

https://eventioz.com/events/actsasrubylit-2010/registrations/new

podés ver la agenda acá:

http://eventioz.com/events/actsasrubylit-2010/agenda

Todos los participantes de las jornadas van a recibir un certificado firmado por las autoridades de La Facultad de Ing. y Ciencias Hídricas.

Contamos con tu presencia!

Versión de ruby en el prompt

Wednesday, 20 October , 2010

Hace un tiempito que vengo usando ruby version manager (rvm) para los proyectos en los que estoy trabajando, una de las cosas que uso es .rvmrc en cada proyecto entonces cuando entro a la carpeta de un proyecto en particular la version de ruby se setea automáticamente, pero hay veces que esto no funciona (por ejemplo cuando abro una nueva terminal con ctrl + t estando ya parado en la carpeta en cuestión) o hay veces que creo que estoy usando una determinada versión de ruby cuando en realidad estoy usando otra (por esto del rvm) entonces lo que hice es agregar la version de ruby al prompt.

Primero cree la función que parsea la versión de ruby, para esto editamos el ~/.bash_profile


function parse_ruby_version {
  ruby -v | cut -d" " -f2
}

Y luego muestro esa función en mi prompt


PS1="${TITLEBAR}\

$BLUE[$LIGHT_GREEN\u$WHITE@\h:$LIGHT_GRAY\w$RED \
\$(parse_ruby_version)$LIGHT_GREEN\$(parse_git_branch)$BLUE]\
$GREEN\$ $LIGHT_GRAY"

Cómo pueden ver yo además de la versión de ruby, tengo el branch de git del repo con el que estoy trabajando, acá les dejo la versión completa:



unction parse_git_branch {
  git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}

function parse_ruby_version {
  ruby -v | cut -d" " -f2
}
function proml {
  local        BLUE="\[33[0;34m\]"
  local         RED="\[33[0;31m\]"
  local   LIGHT_RED="\[33[1;31m\]"
  local       GREEN="\[33[0;32m\]"
  local LIGHT_GREEN="\[33[1;32m\]"
  local       WHITE="\[33[1;37m\]"
  local  LIGHT_GRAY="\[33[0;37m\]"
  case $TERM in
    xterm*)
    TITLEBAR='\[33]0;\u@\h:\w07\]'
    ;;
    *)
    TITLEBAR=""
    ;;
  esac

PS1="${TITLEBAR}\

$BLUE[$LIGHT_GREEN\u$WHITE@\h:$LIGHT_GRAY\w$RED \
\$(parse_ruby_version)$LIGHT_GREEN\$(parse_git_branch)$B\
LUE]\
$GREEN\$ $LIGHT_GRAY"
PS2='> '
PS4='+ '
}
export PROMPT_DIRTRIM=2
proml

Gems mirror local

Friday, 10 September , 2010

Dado que tengo un enlace a internet que no es lo que uno quisiera tener, cada vez que quiero instalar una gema, me demora varios minutos, lo cual es bastante molesto cuando uno está trabajando, por esto decidí poner mi propio gems mirror local, pasemos a la acción:

primero tenemos que editar el archivo de configuración para nuestro gem mirror:

emacs -nw ~/.gemmirrorrc y ponemos el siguiente contenido:

---
- to: /home/gemsmirror/rubygems.mirro
  from: http://gems.rubyforge.org

noten que yo voy a poner mi mirror en la carpeta /home/gemsmirror/rubygems.mirror pero uds la pueden poner en cualquiera que quieran.

luego de esto ejecutamos el comando

gem mirror

y después un largo pero largo rato (a mí esto me llevó como un mes con interrupciones, son como 16GB de gemas) se baja todas las gemas del mirror remoto. Una vez hecho y terminado esto tenemos que configurar nuestro servidor web para que publique el mirror, yo lo hice con apache, así sería:

emacs -nw /etc/apache2/sites-available/gemsmirror

y ponemos lo siguiente


        ServerName gems.localhost
        ServerAdmin root@gems.localhost
        DocumentRoot /home/gemsmirror/rubygems.mirror/

                Options Indexes FollowSymLinks MultiViews
                AllowOverride None

                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
       #AuthName "Dev Access"
       # AuthType Basic
       # AuthBasicProvider file
        # AuthUserFile /opt/coolstack/apache2/conf/htpasswd.users
       # Require user gems
# user: gems
# pass: password
                Order allow,deny
                allow from all

        ErrorLog /var/log/apache2/error-gemsmirror.log
        LogLevel warn
        #CustomLog /var/log/apache2/access-gemsmirror.log combined
        ServerSignature Off

Lo puse sin autenticación por que yo solamente le doy un uso local pero si quieren publicarlo en internet sería razonable ponerle clave.

Ahora habilitamos el site de mirror

a2ensite gemsmirror

y agregamos el host:

emacs -nw /etc/hosts

ponemos lo siguiente:

....
######################################################################
# Gemsmirror local
127.0.0.1 gems.localhost

.....

Ahora recargamos el apache:

/etc/init.d/apache2 reload

y vamos al navegador y chequeamos que se vean el directorio de nuestro mirror en:

http://gems.localhost/

si todo está bien agregamos nuestro mirror local al gemrc:

 emacs -nw ~/.gemrc
---
:sources:
- http://gems.localhost
#- http://gems.rubyforge.org/
#- http://gems.github.com
#- http://gemcutter.org/
#- http://gems.github.com
:bulk_threshold: 1000
:benchmark: false
:update_sources: true
:backtrace: false
:verbose: true

fijensé que yo comenté todos los demás mirrors, de esta manera sólo busca las gemas en el mirror local y al instalar o buscar cualquier cosa la velocidad es increíble :), ahora solo resta pner un cron para que actualice el mirror diariamente (por la noche) y listo.

Eso es todo, enjoy !

Cómo uds. saben ApplicationController trae un método con el cual podemos ocultar datos sensibles que no queremos que aparezcan en los logs (véase la doc ) pero hay veces que además de ocultar los params en los logs, también necesitamos usar este mismo params ya procesado (con los valores FILTERED) en otro lado, por ejemplo para una app que loggea en una tabla todo lo que un user hace (incluido action, controler y params en cuestión) para esto podemos usar filter_parameters(params) que retorna el hash params filtrado, para entender un poco más cómo funciona podés leer los tests de rails en el archivo vendor/rails/actionpack/test/controller/filter_params_test.rb, cómo llegué a este archivo? fácil primero leí el código del método filter_parameter_logging, ahí me ví que crea un método al vuelo llamado filter_parameters y luego de eso simplemente hice un grep:


grep -r filter_parameters vendor/rails/ -n
vendor/rails/actionpack/test/controller/filter_params_test.rb:11:
def test_filter_parameters
vendor/rails/actionpack/test/controller/filter_params_test.rb:13:
assert !@controller.respond_to?(:filter_parameters)
vendor/rails/actionpack/test/controller/filter_params_test.rb:16:
assert @controller.respond_to?(:filter_parameters)
vendor/rails/actionpack/test/controller/filter_params_test.rb:33:
assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter)
vendor/rails/actionpack/test/controller/filter_params_test.rb:43:
assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter)
vendor/rails/actionpack/test/controller/filter_params_test.rb:47:
def test_filter_parameters_is_protected
vendor/rails/actionpack/test/controller/filter_params_test.rb:49:
assert !FilterParamController.action_methods.include?('filter_parameters')
vendor/rails/actionpack/test/controller/filter_params_test.rb:50:
 assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) }
vendor/rails/actionpack/lib/action_controller/base.rb:486:
define_method(:filter_parameters) do |unfiltered_parameters|
vendor/rails/actionpack/lib/action_controller/base.rb:493:
  filtered_parameters[key] = filter_parameters(value)
vendor/rails/actionpack/lib/action_controller/base.rb:498:
 filter_parameters(item)
vendor/rails/actionpack/lib/action_controller/base.rb:515:
protected :filter_parameters
vendor/rails/actionpack/lib/action_controller/base.rb:1319:
  parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup

Bueno cortito, pero útil ;)

MongoId y Rails3

Tuesday, 11 May , 2010

Bueno últimamente vengo posteando con bastante delay (espero que eso cambie),
en esta ocasión voy a hacer un pequeño post acerca de mis experiencias migrando
una app de rails 2.3.4 y Active record con Mysql a Rails3, MongoId y Ruby 1.9.1,
sí, así completita la cosa, la aplicación no es demasiado grande así que viene bien
para probar esta nueva arquitectura.
No voy entrar en los detalles de como instalar rails3 y esas cosas por que ya existen
varios posts acerca del tema y no me quiero repetir, pasemos derecho al tema mongoid
Mongoid es un ODM(Object-Document-Mapper) para mongoDB, tiene varias cosas buenas algunas de las que me llamaron
la atención a mí es que no necesitamos migrations para para modificar nuestros documentos
(en sql diríamos tablas) además los campos (columnas) los podemos ver directamente abriendo
el modelo.


class Order
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::XmlBuilder

  field :external_id, :type => Integer
  field :account_id, :type => Integer
  field :order_type, :type => String
  field :posted, :type => Boolean
  field :send_result_notification, :type => Boolean
  field :file_duration_ms, :type => Integer
  field :created_at, :type => DateTime
  field :updated_at, :type => DateTime

  embeds_one :result, :class_name => "OrderResult"

  #  -----------------------------------------------------------------------------------------------
  #  Viejo named_scope reemplazado por mongoid queries
  #
  #  named_scope :pendings, :conditions => "NOT EXISTS (SELECT * FROM
  #   order_results WHERE
  #    order.id = order_results.transcription_id)
  #    AND posted_id IS NOT NULL AND
  #    orders.posted = 1"
  #
  # ------------------------------------------------------------------------------------------------

  def self.without_results
    result_ids = TranscriptionResult.criteria.only(:id).map{|id| }
    Order.where(:_id.nin => result_ids)
  end

  def self.pendings
    without_results.where(:posted => true)
  end
end

Otra de las ventajas es que los criterios son anidables con lo cual funcionarían
como una especie de scope de Active Record, fijensé como quedó el viejo named_scope
con mongoid, mucho más claro que antes para mi gusto.
Otra de las ventajas de mongoID en particular es que incluye ActiveModel, con lo
cual tenemos las mismas validaciones que en Rails, también podemos definir callbacks de forma similar que en Active Record.

Pasemos a explicar un poquito:
Lo que yo necesito son los transcriptions que no tengan un transcription_result asociado y dónde posted sea true.


result_ids =OrderResult.criteria.only(:id)
= > #[:id]}, @klass=OrderResult, @documents=[]>


con esto saco la lista de results, sólo me traigo los ids por que no necesito
otra cosa y le aplico un map para obtener una array con la lista de ids (cómo pueden ver eso devuelve un objeto Mongoid::Criteria) con esto ya tengo los transcriptions que no tienen results (el método without_results). Después una vez que tengo esto ya es más fácil
generar los pendings (transcriptions sin results y con posted == true).
Nota: :_id.nin quiere decir “los _id que no estén incluidos en (not included in)”
para más detalles pueden ver la doc de queries de mongoid

PD: MongoId también tiene named_scopes dejo pendiente para el próximo refactoring pasar esto a named_scope de mongoid.

Nos vemos en el próximo post.

Estoy trabajando en un proyecto en el cual estamos usando stories como  framework para los tests de aceptación, resulta que me encontré con un pequeño incoveniente: los tests eran un poco lentos y no tenía forma de correr un test para un sólo scenario, si bien como son tests de aceptación o integración sabemos que hay que correrlos todos juntos, a veces cuando hay un error o failure es útil poder correr sólamente ese test y no todos. Entonces comencé a escribir un pequeño script para poder correr un sólo scenario de un story en particular, el script creció un poquito así que decidí crear una pequeña gema que se llama “Forrest” así que les muestro un poquito como funciona, es muy simple:

$ sudo gem install forrest

Successfully installed forrest-0.1.1
1 gem installed
Installing ri documentation for forrest-0.1.1...
Installing RDoc documentation for forrest-0.1.1...
Successfully installed forrest-0.1.11 gem installedInstalling ri documentation for forrest-0.1.1...Installing RDoc documentation for forrest-0.1.1...
$ cd /rails/my-project
$ forrest test/stories/auth-stories-test.rb

+ ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICanStart
ManagingMyServices
|__ ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICanStart
ManagingMyServices#test_Given_bad_params
|__ ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICanStart
ManagingMyServices#test_Given_good_params

Para correr un scenario en particular simplemente copiamos y pegamos uno de las lista impresa más arriba y ejecutamos forrest así:

forrest ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoI
CanStartManagingMyServices#test_Given_good_params

o por ejemplo si estamos buscando un storie en particular podemos usar grep:

$ forrest test/stories/client_site_stories_test.rb | grep -y good_params

|__ ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICan
StartManagingMyServices#test_Given_good_params

Otra opción es correr todos los scenarios de un story en particular, para esto tenemos que ejecutar forrest con el nombre del story, que también lo podemos copiar del listado impreso
cuando ejecutamos forrest con un archivo de stories como argumento, asi:


forrest ClientSiteStoriesTest::TestAsAnAccountIWantToLoginToMyClientSiteSoICanStart
ManagingMyServices

Otro inconveniente era que cuando había un error se mostraba toda lá página de error de rails lo cual era bastante molesto, con forresto los errores se ven un poquito más lindos:

forrest CustomerStoriesTest::TestAsACustomerIWantToResetMyPasswordIfIForgotItSo
ThatICanRecoverAccessToTheSite#test_Given_an_exisitng_account_email_but_without_
fill_in_the_company
==> Runing scenario...
================================================================================

- As a customer I want to reset my password if I forgot it so that I can recover 
  access to the site 

    Given an exisitng account email but without fill in the company
================================================================================

Finished in 0.061427 seconds.

  1) Error:
NoMethodError: undefined method `closed?' for nil:NilClass...
/usr/lib/ruby/1.8/net/http.rb:1060:in `request'
    /usr/lib/ruby/1.8/net/http.rb:772:in `get'
    lib/active_resource_extensions.rb:77:in `last'
    test/stories/customer_stories_test.rb:9:in `setup'

Cabe destacar que cuando estaba escribiendo forrest me encontré con este bug en rails:
https://rails.lighthouseapp.com/projects/8994/tickets/3153-actioncontrollerintegrationsession-broken-in-234

que si no lo fixean no van a poder usar forrest, por lo menos yo no pude.

Bueno esto fue todo, cualquier sugerencia comenten!

Colaborando con Ruby

Wednesday, 16 December , 2009

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…

Follow

Get every new post delivered to your Inbox.