How to Get Class-Object from String "A::B::C" in Ruby

How do I get class-object from string A::B::C in Ruby?

You'll have to manually "parse" the colons yourself and call const_get on the parent module/class:

ruby-1.9.1-p378 > class A
ruby-1.9.1-p378 ?> class B
ruby-1.9.1-p378 ?> end
ruby-1.9.1-p378 ?> end
=> nil
ruby-1.9.1-p378 > A.const_get 'B'
=> A::B

Someone has written a qualified_const_get that may be of interest.

Get a class by name in Ruby?

If you want something simple that handles just your special case you can write

Object.const_get("Admin").const_get("MetaDatasController")

But if you want something more general, split the string on :: and resolve the names one after the other:

def class_from_string(str)
str.split('::').inject(Object) do |mod, class_name|
mod.const_get(class_name)
end
end

the_class = class_from_string("Admin::MetaDatasController")

On the first iteration Object is asked for the constant Admin and returns the Admin module or class, then on the second iteration that module or class is asked for the constant MetaDatasController, and returns that class. Since there are no more components that class is returned from the method (if there had been more components it would have iterated until it found the last).

Cast between String and Classname

This solution is better than eval as you are evaluating params hash that might be manipulated by the user and could contain harmful actions. As a general rule: Never evaluate user input directly, that's a big security hole.

# Monkey patch for String class
class String
def to_class
klass = Kernel.const_get(self)
klass.is_a?(Class) ? klass : nil
rescue NameError
nil
end
end

# Examples
"Fixnum".to_class #=> Fixnum
"Something".to_class #=> nil

Update - a better version that works with namespaces:

 # Monkey patch for String class
class String
def to_class
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

Ruby String#to_class

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)

How to dynamically get Class in Ruby just like we can call send to trigger a method dynamically?

Ruby actually allows you get the class object from the argument in the string by using const_get. For example

klass = Object.const_get("Person") # => return Person class

How do I get the class of a BasicObject instance?

If you can upgrade to Ruby 2.0, you don't need to implement anything at all:

>> Kernel.instance_method(:class).bind(BasicObject.new).call
=> BasicObject

Why do I get an error trying to refer a nested class in Ruby?

Well, yes, if you define your test method to return ClassA::ClassB.new :-)

You could also play around with const_missing so that it calls ClassA.const_get.

Otherwise ClassB is not in the current scope, which at that point is only ClassA::ClassE and Object. When you first open ClassA, then ClassE, the lookup for ClassB is done first in ClassA::ClassE, then in ClassA (where it is found) and would also look in Object.

Creating a function in ruby

Hello, i have some problem with creating a function to my Rails app.

Uh oh, right there we run into our first misunderstanding: Ruby is not a functional programming language. It is an object-oriented programming language. There is no such thing as a function in Ruby. Ruby only has methods that live on classes.

str = 'string here'
puts "It's within the range!" if str.within_range?(3..30)

Here we can see that you obviously intend to have your method available on string objects. In order for your method to be available on string objects, it needs to be defined on the String class (or any of its ancestors).

To do that i added this into my application helper:

def within_range?(range)
if is_a?(String)
range.include?(size)
elsif is_a?(Integer)
range.include?(self)
end
end

Here, you are adding the within_range? method to the anymous top-level object and not to the String class. Adding a method to the anymous top-level object, also makes it "magically" available as a private method on the Object class which is the superclass of (almost) all classes. Therefore, within_range? is now available on all objects, but it is private, so you can only call it with an implicit receiver.

Which is why I don't understand why you are getting this error:

But i get this error:

undefined method `within_range?' for "":String

There's two things wrong here: first, the object that Ruby reports the error on should be "string here":String, not "":String. This means that you were actually calling the within_range? method on an empty string and not on the string 'string here' as your code sample shows. Which means that somewhere else in your code base, before the code sample that you posted here, within_range? already gets called on an empty string.

The second problem is that you are getting the totally wrong error message. Ruby tells you that Ruby couldn't find a within_range? method for strings, i.e. that there is no within_range? method on the String class or any of its superclasses including Object. But there should be! You did (although accidentally) define a private method on Object. The error message that you should be getting is this:

NoMethodError: private method `within_range?' called for "string here":String

This means that the code that defines the method never actually got executed. I am not familiar enough with Ruby on Rails to diagnose why that may be the case, though.

But now on to the contents of the code itself. Like I hinted at above, you need to add this code to the String class. And actually, inside your method, you are dealing with both strings and integers, so you need to add the code to a common superclass of String and Integer and there is only one and that is Object:

class Object
def within_range?(range)
if is_a?(String) then range.include?(size)
elsif is_a?(Integer) then range.include?(self) end
end
end

[I took the liberty of removing a couple of extra parentheses, selfs and newlines.]

There are a couple of problems with this. First: is_a? in Ruby is a Code Smell. It almost always means that you are either doing something wrong or at least something "un-Rubyish", like trying to re-implement static typing. (Don't get me wrong: I like static typing, but if I need it, I use a more appropriate language than Ruby.)

In general, if you find yourself using is_a? or its cousins kind_of? and Module#===, you should take a step back and reconsider your design.

Another problem with your code is that you have a condition that examines self. This is even easier to see when we refactor the code to use a case expression instead of an if expression:

class Object
def within_range?(range)
case self
when String then range.include?(size)
when Integer then range.include?(self) end
end
end

Here, it is easy to see that the object is looking at itself. It should never need to do that! It should always already know everything there is to know about itself. That's as if you had to look in the mirror to know what you are wearing: you shouldn't need to do that, after all you put the clothes on yourself! (And if you need to do that, it is probably the result of some bad decision the night before, just like in programming ...)

A third smell here is the fact that you are doing different things, depending on the type of an object. That's exactly what polymorphism is for: if you say foo.bar, Ruby will call a different version of bar, depending on what the type of foo is. There's no need for you to re-implement this yourself, it's already there.

So, let's step back: what is the method actually doing? Well, if it's called on a string, then it does one thing and if it's called on an integer, it does another thing. So, we are lazy and let Ruby do the work of figuring out which of the two to run:

class String
def within_range?(range)
range.include?(size)
end
end

class Integer
def within_range?(range)
range.include?(self)
end
end

This is already much simpler and much clearer. And there is another advantage: now, if you call within_range? on an object that it doesn't know about, it will actually tell you that it doesn't know what to do by raising a NoMethodError:

[].within_range?(3..30)
# => NoMethodError: undefined method `within_range?' for []:Array

The old version was missing an else clause, so it would have just returned nil which evaluates to false in a boolean context, which means that the error would probably have gone undetected.

There is even more we can do: there is actually nothing here that restricts the method from only working with whole numbers, it could just as easily work with floating point numbers, rationals or even complex numbers. So, instead of defining the second method on Integer, we could instead define it on Numeric.

But if you think about it: there actually isn't even anything that requires it to be a number at all, it could in fact be any object, so we could even define it on Object.

This brings me to the last two problems I have with this method, which are related to each other: I think the name for the String version is misleading, and I don't like that the method does completely different things despite its name being the same.

For an integer (or any object, if we define the method on Object), the method checks whether or not the integer (or object) itself is within the range. But for a string, the method does not check whether or not the string itself is within the range, rather it checks whether the length of the string is within the range. This is completely non-obvious and surprising to me:

  2.within_range?(1..3)     # => true
'B'.within_range?('A'..'C') # => false

I, and I suspect pretty much everybody else who reads that piece of code, would expect both of those to be true. The fact that B is not between A and C is completely confusing.

That's what I meant above: the method does two different things despite having the same name. Also, in the case of the string, the method name implies that it checks whether the string is within a certain range, but it actually checks whether the string length is within an certain range, and there is absolutely no indication in the method name that this is the case.

A much better name would be length_within_range?, and you would call it like so:

'B'.length_within_range?(1..2) # => true
'B'.within_range?('A'..'C') # => true

[Assuming that within_range? is defined on Object.]

This makes it very clear to everyone reading the code that we are comparing the length of the string and not the string itself.

However, now there really isn't much difference between

str.length_within_range?(3..30)
str.length.within_range?(3..30)

Which means we can get rid of the string version altogether, leaving us only with the Object version.

The Object version, in turn, doesn't really do anything except flipping argument and receiver around, so we might just as well write

(3..30).include?(str.length)

and get rid of the whole thing altogether.

How do I get the name of a Ruby class?

You want to call .name on the object's class:

result.class.name

Test if variable matches any of several strings w/o long if-elsif chain, or case-when

Perhaps you didn't know that you can put multiple conditions on a single case:

case mystr
when "abc", "def", "ghi", "xyz"
..
end

But for this specific string-based test, I would use regex:

if mystr =~ /\A(?:abc|def|ghi|xyz)\z/

If you don't want to construct a regex, and you don't want a case statement, you can create an array of objects and use Array#include? test to see if the object is in the array:

if [a,b,c,d].include?( o )

or, by monkey-patching Object, you can even turn it around:

class Object
def in?( *values )
values.include?( self )
end
end

if o.in?( a, b, c, d )


Related Topics



Leave a reply



Submit