How do I check if a class is defined?

How do I turn a string into a class name, but only if that class already exists?

If Amber is already a class, I can get from a string to the class via:

Object.const_get("Amber")

or (in Rails)

"Amber".constantize

But either of these will fail with NameError: uninitialized constant Amber if Amber is not already a class.

My first thought is to use the defined? method, but it doesn't discriminate between classes that already exist and those that don't:

>> defined?("Object".constantize)
=> "method"
>> defined?("AClassNameThatCouldNotPossiblyExist".constantize)
=> "method"

So how do I test if a string names a class before I try to convert it? (Okay, how about a begin/rescue block to catch NameError errors? Too ugly? I agree...)


How about const_defined??

Remember in Rails, there is auto-loading in development mode, so it can be tricky when you are testing it out:

>> Object.const_defined?('Account')
=> false
>> Account
=> Account(id: integer, username: string, google_api_key: string, created_at: datetime, updated_at: datetime, is_active: boolean, randomize_search_results: boolean, contact_url: string, hide_featured_results: boolean, paginate_search_results: boolean)
>> Object.const_defined?('Account')
=> true

In rails it's really easy:

amber = "Amber".constantize rescue nil
if amber # nil result in false
    # your code here
end

Inspired by @ctcherry's response above, here's a 'safe class method send', where class_name is a string. If class_name doesn't name a class, it returns nil.

def class_send(class_name, method, *args)
  Object.const_defined?(class_name) ? Object.const_get(class_name).send(method, *args) : nil
end

An even safer version which invokes method only if class_name responds to it:

def class_send(class_name, method, *args)
  return nil unless Object.const_defined?(class_name)
  c = Object.const_get(class_name)
  c.respond_to?(method) ? c.send(method, *args) : nil
end

It would appear that all the answers using the Object.const_defined? method are flawed. If the class in question has not been loaded yet, due to lazy loading, then the assertion will fail. The only way to achieve this definitively is like so:

  validate :adapter_exists

  def adapter_exists
    # cannot use const_defined because of lazy loading it seems
    Object.const_get("Irs::#{adapter_name}")
  rescue NameError => e
    errors.add(:adapter_name, 'does not have an IrsAdapter')
  end