Ruby String # to_class - PullRequest
       39

Ruby String # to_class

2 голосов
/ 19 сентября 2009

Взятый из предыдущего поста с некоторыми изменениями для ответа на комментарий sepp2k о пространствах имен, я реализовал метод String # to_class. Я делюсь здесь кодом и верю, что его можно каким-то образом изменить, особенно счетчик «i». Ваши комментарии приветствуются.

 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 ]

6 голосов
/ 19 сентября 2009

Я бы взглянул на ActiveSupport::CoreExtensions::String::Inflections, в частности, это constantize метод:

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
4 голосов
/ 20 сентября 2009

Я провел несколько тестов по любопытству, и мое решение очень медленное! Вот реорганизованное решение с тестами, надеюсь, это поможет.

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)
2 голосов
/ 20 сентября 2009

Вы можете использовать рекурсию:

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
...