views:

261

answers:

3

Taken from a previous post with some modifications to respond to sepp2k's comment about namespaces, I have implemented String#to_class method. I'm sharing the code here and I do believe that it could be refactored someway specially the "i" counter. Your comments are appreciated.

 class String
   def to_class
     chain = self.split "::"
     i=0
     res = chain.inject(Module) do |ans,obj|
       break if ans.nil?
       i+=1
       klass = ans.const_get(obj)
       # Make sure the current obj is a valid class 
       # Or it's a module but not the last element, 
       # as the last element should be a class
       klass.is_a?(Class) || (klass.is_a?(Module) and i != chain.length) ? klass : nil
     end
   rescue NameError
     nil
   end
 end

 #Tests that should be passed.
 assert_equal(Fixnum,"Fixnum".to_class)
 assert_equal(M::C,"M::C".to_class)
 assert_nil "Math".to_class
 assert_nil "Math::PI".to_class
 assert_nil "Something".to_class
+3  A: 

I would take a look at ActiveSupport::CoreExtensions::String::Inflections specifically it's constantize method:

def constantize(camel_cased_word)
  names = camel_cased_word.split('::')
  names.shift if names.empty? || names.first.empty?

  constant = Object
  names.each do |name|
    constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
  end
  constant
end
Michael Sepcot
I have added few tests to give a better specs. I'm not seeking constantizing things, yet good catch.
khelll
+1  A: 

You can use recursion:

class String
  def to_class(parent = Kernel)
    chain = self.split "::"
    klass = parent.const_get chain.shift
    return chain.size < 1 ? (klass.is_a?(Class) ? klass : nil) : chain.join("::").to_class(klass)
    rescue
      nil
  end  
end
Erwan Barrier
+1 for functional programming
khelll
+2  A: 

I ran some benchmarks by curiosity and my solution is very slow! here is a refactored solution with benchmarks, hope that helps.

require "benchmark"

class String
  def to_class_recursive
    chain = self.split "::"
    klass = parent.const_get chain.shift
    return chain.size < 1 ? (klass.is_a?(Class) ? klass : nil) : chain.join("::").to_class(klass)
  rescue
    nil
  end

  def to_class_original
    chain = self.split "::"
    i=0
    res = chain.inject(Module) do |ans,obj|
      break if ans.nil?
      i+=1
      klass = ans.const_get(obj)
      # Make sure the current obj is a valid class 
      # Or it's a module but not the last element, 
      # as the last element should be a class
      klass.is_a?(Class) || (klass.is_a?(Module) and i != chain.length) ? klass : nil
    end
  rescue NameError
    nil
  end

  def to_class_refactored
    chain = self.split "::"
    klass = Kernel
    chain.each do |klass_string|
      klass = klass.const_get klass_string
    end
    klass.is_a?(Class) ? klass : nil
  rescue NameError
    nil
  end
end

module M
  class C
  end
end

n = 100000
class_string = "M::C"
Benchmark.bm(20) do |x|
  x.report("to_class_recursive") { n.times { class_string.to_class_recursive } }
  x.report("to_class_original") { n.times { class_string.to_class_original } }
  x.report("to_class_refactored") { n.times { class_string.to_class_refactored } }
end

#                           user     system      total        real
# to_class_recursive    2.430000   0.170000   2.600000 (  2.701991)
# to_class_original     1.000000   0.010000   1.010000 (  1.049478)
# to_class_refactored   0.570000   0.000000   0.570000 (  0.587346)
Erwan Barrier
Good Job! I would use Benchmark.bmbm instead to warm up. Thanks!
khelll