Skip to content


Instanciar clase desde String en Ruby

Investigando un poco el comportamiento de acts_as_scribe, me surgió la necesidad de instanciar un clase conociendo el nombre de la misma. Algo que en java conocía como se realizaba:

Object o = Class.forName("className").newInstance()

lo curioso es que vi que no necesitaba esta solución, que mi problema era otro :) , sin embargo la curiosidad pudo más que la necesidad.

Esperaba utilizar para ello la misma clase: Class, sin embargo descubrí que no podía hacerlo así. En Ruby el camino es distinto. Un post en stackoverflow me llevó a encontrar una solución múltiple, aquí las distintas posibilidades:

Object::const_get('String').new()

class_name = 'String'
eval(class_name).new

Module.const_get('Array').new

Y un helper:

"String".constantize.new

Module.const_get no funciona con clases anidadas en módulos, digamos que no admite la sintaxis Admin::Folder, debido a que sólo reconoce sintaxis válidas para dar nombre a una constante.

eval, por el contrario sí permite nombres de clases con namespaces, aunque sufrimos un decrimento en el rendimiento.

Podríamos comprobar el rendimiento de todas ellas con un simple script:

#class_instanciation.rb
#!/usr/bin/env ruby
#
#  Created by  on 2008-11-24.

require 'benchmark'
require 'qualified_const_get'

n = 1000000

Benchmark.bmbm(10) do |rpt|
rpt.report("simple") do
n.times {Array.new}
end

rpt.report("Object::const_get") do
n.times {Object::const_get('Array').new()}
end

rpt.report("Kernel.const_get") do
n.times {Kernel.const_get('Array').new}
end

rpt.report("Kernel.qualified_const_get") do
n.times {Kernel.qualified_const_get('Array').new}
end

rpt.report("eval") do
n.times {eval('Array').new}
end

# no funciona!!
#rpt.report("eval") do
#  n.times {"String".constantize.new}
#end
end
#qualified_const_get.rb
module Kernel
def qualified_const_get(str)
path = str.to_s.split('::')
from_root = path[0].empty?
if from_root
from_root = []
path = path[1..-1]
else
start_ns = ((Class === self)||(Module === self)) ? self : self.class
from_root = start_ns.to_s.split('::')
end
until from_root.empty?
begin
return (from_root+path).inject(Object) { |ns,name| ns.const_get(name) }
rescue NameError
from_root.delete_at(-1)
end
end
path.inject(Object) { |ns,name| ns.const_get(name) }
end
end

Los resultados obtenidos los podéis ver en la siguiente gráfica:

en ella podemos apreciar que si utilizamos const_get se tarda aproximadamente 2 veces más que con la creación normal; que el uso de eval nos hace emplear 5 veces más tiempo y que sin duda la implementación de qualified_const_get no es nada buena, dado que nos hace emplear unos 10sg! osea unas 15 veces más.

La recomendación sería utilizar const_get siempre que se pueda, es decir mientras nuestra clase no tenga namespaces y eval cuando no quede más remedio. El caso de qualified_const_get, me parece poco apropiado para cualquier caso.

Por otro lado, el caso de constantize, no terminó de funcionar, por lo que es imposible compararlo.

Posted in RubyOnRails, Tips & Tricks.

Tagged with , , .

You might also like

Debugear en Rails con ruby-debug
Investigando podemos descubrir diferentes formas para debugear en RoR, para el caso he elegido hacerlo...
Ruby recipe: comprobar si un array tiene un objeto
El otro día andaba buscando cómo determinar si un array/colección contiene un objeto determinado,...
Leopard, mi primer mal trago
Usar Leopard durante los primeros días ha sido agotador tanto física como sicológicamente. Desde...
Instalación del plugin: acts_as_commentable
Este fin de semana he retomado mi idea de crear una herramienta para blogs, pero tenía la intención...
Grab this Widget

0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.