Jak sprawdzić, czy zdefiniowano klasę?


58

Jak zmienić ciąg znaków w nazwę klasy, ale tylko wtedy, gdy klasa już istnieje?

Jeśli Amber już klasy, mogę z ciągiem do klasy poprzez:

Object.const_get("Amber") 

lub (w Rails)

"Amber".constantize 

ale albo z nich nie powiedzie się NameError: uninitialized constant Amber jeśli Amber nie jest jeszcze klasą.

Moja pierwsza myśl jest użycie metody defined?, ale to nie dyskryminuje klas, które już istnieją i te, które nie:

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

Więc jak mam przetestować Jeżeli nazw strun klasą zanim spróbuję go przekonwertować? (Okay, a co powiesz na blok begin/rescue, aby złapać błędy NameError? Zbyt brzydki? Zgadzam się ...)

  0

'' zdefiniowane w tym przykładzie jest dokładnie robi to, co ma robić: Sprawdza, czy 'constantize' metoda na obiekcie String jest zdefiniowana. Nie obchodzi go, czy ciąg zawiera "Obiekt" lub "AClassNameThatCouldNotPossiblyExist". 08 maj. 172017-05-08 15:34:40

105

Co powiecie na const_defined??

Zapamiętaj w Rails, nie ma automatycznego ładowania w trybie rozwoju, więc może to być trudne, gdy testujesz go:

>> 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 
+2

perfect - thanks. co do automatycznego programu ładującego, IIRC, jest sposób, aby dowiedzieć się, co znajduje się na liście autoloadera. Wykopię to, jeśli okaże się, że jest problem. 22 kwi. 112011-04-22 18:36:46

  0

dzięki, właśnie to, czego potrzebuję = D 17 kwi. 152015-04-17 04:55:35

+3

To również pasuje do rzeczy, które nie są klasami. 03 wrz. 152015-09-03 17:47:26


10

Zainspirowany użytkownika @ ctcherry odpowiedzi powyżej, tu jest „bezpieczna metoda klasy wyślij ", gdzie class_name jest ciągiem znaków. Jeśli class_name nie nazwie klasy, zwraca zero.

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

Jeszcze bezpieczniej wersja, która wywołuje method Tylko jeśli class_name reaguje na nią:

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 
+2

p.s.: jeśli podoba ci się ta odpowiedź, proszę odpowiedzieć w górę na odpowiedź ctcherry, ponieważ to właśnie wskazało mi właściwy kierunek. 22 kwi. 112011-04-22 18:54:49


0

Utworzyłem walidator aby sprawdzić, czy ciąg znaków jest poprawną nazwą klasy (lub lista oddzielonych przecinkami prawidłowych nazw klas):

class ClassValidator < ActiveModel::EachValidator 
    def validate_each(record,attribute,value) 
    unless value.split(',').map { |s| s.strip.constantize.is_a?(Class) rescue false }.all? 
     record.errors.add attribute, 'must be a valid Ruby class name (comma-separated list allowed)' 
    end 
    end 
end 

1

Kolejne podejście, na wypadek gdybyś również chciał zdobyć klasę. Zwróci wartość zero, jeśli klasa nie jest zdefiniowana, więc nie musisz wychwytywać wyjątków.

class String 
    def to_class(class_name) 
    begin 
     class_name = class_name.classify (optional bonus feature if using Rails) 
     Object.const_get(class_name) 
    rescue 
     # swallow as we want to return nil 
    end 
    end 
end 

> 'Article'.to_class 
class Article 

> 'NoSuchThing'.to_class 
nil 

# use it to check if defined 
> puts 'Hello yes this is class' if 'Article'.to_class 
Hello yes this is class 

11

w szynach to naprawdę proste:

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

"Ratowanie" było pomocne, ponieważ czasami można rozładować stałe i sprawdzenie za pomocą 'const_defined? 'Będzie fałszywe. 17 cze. 162016-06-17 18:52:29

  0

Bardzo dziękuję! 11 lut. 172017-02-11 12:16:40

  0

Pomijanie wyjątków nie jest zalecane, czytaj więcej tutaj: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions 11 wrz. 172017-09-11 19:23:25

  0

@AndrewK: w Ruby ratowanie jest używane bardzo często - ofc Zgadzam się, że to niedobrze; w świecie Elixir staramy się tego nie robić, jeśli nie ma takiej potrzeby, ale widziałem, że wiele osób korzysta z akcji ratunkowej w Rubim 12 wrz. 172017-09-12 19:43:44

  0

@Eiji, Zgoda. Chciałem tylko wspomnieć, że ludzie, którzy są nowi w Ruby, nie wiedzą, że to anty-wzór i należy ich unikać. 12 wrz. 172017-09-12 20:56:38


2

Wydaje się, że wszystkie odpowiedzi korzystając z metody Object.const_defined? są błędne. Jeśli dana klasa nie została jeszcze załadowana, z powodu leniwego ładowania, asercja nie powiedzie się. Jedynym sposobem osiągnięcia tego celu jest ostatecznie tak:?

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