Remada de Sabado, Destino: primer puente del Leyes
Saturday, 17 December , 2011
* 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 !
Usando filter_parameters en ApplicationController con Rails 2.3
Monday, 17 May , 2010
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.
Forrest, un runner para stories con rails
Monday, 22 March , 2010
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:
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…



