Czy istnieje sposób, aby uzyskać kolekcję wszystkich modeli w aplikacji Rails?


201

Czy istnieje sposób, aby uzyskać kolekcję wszystkich modeli w aplikacji Rails?

Zasadniczo mogę zrobić:

Models.each do |model|
  puts model.class.name
end

1
Jeśli chcesz zebrać wszystkie modele, w tym modele silników / szyn Rails, zapoznaj się z odpowiedzią @jaime
— Andrei

Nie działa na szynach 5.1
— aks

Odpowiedzi:


98

EDYCJA: Spójrz na komentarze i inne odpowiedzi. Są mądrzejsze odpowiedzi niż ta! Lub spróbuj ulepszyć ten jako wiki społeczności.

Modele nie rejestrują się w obiekcie głównym, więc nie, Rails nie ma listy modeli.

Ale nadal możesz zajrzeć do zawartości katalogu modeli aplikacji ...

Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
  # ...
end

EDYCJA: Innym (dzikim) pomysłem byłoby użycie refleksji Ruby do wyszukiwania wszystkich klas rozszerzających ActiveRecord :: Base. Nie wiem jednak, jak wymienić wszystkie klasy ...

EDYCJA: Dla zabawy znalazłem sposób, aby wymienić wszystkie klasy

Module.constants.select { |c| (eval c).is_a? Class }

EDYCJA: W końcu udało się wymienić wszystkie modele bez patrzenia na katalogi

Module.constants.select do |constant_name|
  constant = eval constant_name
  if not constant.nil? and constant.is_a? Class and constant.superclass == ActiveRecord::Base
    constant
  end
end

Jeśli chcesz również obsługiwać klasę pochodną, ​​musisz przetestować cały łańcuch nadklasy. Zrobiłem to, dodając metodę do klasy Class:

class Class
  def extend?(klass)
    not superclass.nil? and ( superclass == klass or superclass.extend? klass )
  end
end

def models 
  Module.constants.select do |constant_name|
    constant = eval constant_name
    if not constant.nil? and constant.is_a? Class and constant.extend? ActiveRecord::Base
    constant
    end
  end
end

6
Do twojej wiadomości, obliczyłem obie metody dla zabawy. Wyszukiwanie katalogów jest o rząd wielkości szybsze niż wyszukiwanie w klasach. To pewnie było oczywiste, ale teraz już wiesz :)
— Edward Anderson

9
Ważne jest również, aby pamiętać, że wyszukiwanie modeli za pomocą metod stałych nie obejmie niczego, do czego nie odwoływano się od czasu uruchomienia aplikacji, ponieważ ładuje modele tylko na żądanie.
— Edward Anderson

4
Wolę „Kernel.const_get stała_nazwa” niż „eval stała_nazwa”.
— Jeremy Weathers

3
RAILS_ROOTnie jest już dostępny w Rails 3. Zamiast tego użyjDir.glob(Rails.root.join('app/models/*'))
— fanaugen

1
W rzeczywistości modele rejestrują się jako potomkowie ActiveRecord::Baseteraz, więc jeśli chcesz załadować wszystkie modele, możesz je łatwo iterować - zobacz moją odpowiedź poniżej.
— sj26,

393

Cała odpowiedź dla szyn 3, 4 i 5 brzmi:

Jeśli cache_classesjest wyłączone (domyślnie jest wyłączone w fazie rozwoju, ale włączone w produkcji):

Rails.application.eager_load!

Następnie:

ActiveRecord::Base.descendants

Dzięki temu wszystkie modele w aplikacji, niezależnie od tego, gdzie się znajdują, są załadowane, a wszystkie używane klejnoty, które udostępniają modele, są również ładowane.

Powinno to również działać na klasach, które dziedziczą ActiveRecord::Base, jak ApplicationRecordw Railsach 5, i zwracają tylko to poddrzewo potomków:

ApplicationRecord.descendants

Jeśli chcesz dowiedzieć się więcej o tym, jak to zrobić, sprawdź ActiveSupport :: DescendantsTracker .


33
Niesamowite! To powinna być zaakceptowana odpowiedź. Dla każdego z zastosowaniem tego w zadaniu natarcia: Dodaj zadanie zależą :environmentdla eager_load!do pracy.
— Jo Liss,

1
Lub, jako nieco szybsza alternatywa Rails.application.eager_load!, możesz po prostu załadować modele:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
— Ajedi32,

5
@ Ajedi32, który nie jest kompletny, modele można zdefiniować poza tymi katalogami, szczególnie przy użyciu silników z modelami. Nieco lepiej, przynajmniej globalnie wszystkie Rails.paths["app/models"].existentkatalogi. Chętne ładowanie całej aplikacji jest bardziej kompletną odpowiedzią i upewni się, że absolutnie nie ma już nic do zdefiniowania modeli.
— sj26

2
Rozumiem, co oznacza sj26, ale może jest mały błąd: o ile wiem w środowisku programistycznym cache_classes jest wyłączone (false), dlatego musisz ręcznie zapragnąć załadować aplikację, aby uzyskać dostęp do wszystkich modeli. wyjaśniono tutaj
— masciugo

3
@ Ajedi32 ponownie, nie pełna odpowiedź. Jeśli chcesz ładować tylko modele, spróbuj:Rails.application.paths["app/models"].eager_load!
— sj26 29.10.13

119

Na wypadek, gdyby ktoś natknął się na to, mam inne rozwiązanie, nie polegając na czytaniu reżimu ani rozszerzaniu klasy Class ...

ActiveRecord::Base.send :subclasses

Zwróci to tablicę klas. Więc możesz to zrobić

ActiveRecord::Base.send(:subclasses).map(&:name)

8
dlaczego nie używasz, ActiveRecord::Base.subclassesale musisz używać send? Wygląda też na to, że musisz „dotknąć” modelu, zanim się pojawi, na przykład c = Category.newi się pojawi. W przeciwnym razie nie będzie.
— nonpolarity

52
W Rails 3 zmieniono to naActiveRecord::Base.descendants
— Tobias Cohen

3
Musisz użyć opcji „wyślij”, ponieważ element: podklasy jest chroniony.
— Kevin Rood

11
Dzięki za wskazówkę Rails 3. Dla każdego, kto przyjdzie, nadal musisz „dotknąć” modeli, zanim ActiveRecord::Base.descendantsje wypisze.
— nfm

3
Technicznie w Rails 3 masz podklasy i potomków, oznaczają one różne rzeczy.
— sj26

67
ActiveRecord::Base.connection.tables.map do |model|
  model.capitalize.singularize.camelize
end

wróci

["Article", "MenuItem", "Post", "ZebraStripePerson"]

Informacje dodatkowe Jeśli chcesz wywołać metodę na nazwie obiektu bez modelu: ciąg znaków nieznana metoda lub błędy zmiennych, użyj tego

model.classify.constantize.attribute_names

8
Dzięki temu otrzymasz wszystkie tabele, nie tylko modele, ponieważ niektóre tabele nie zawsze mają powiązane modele.
— courtimas

Ta odpowiedź powinna być uważana za niepoprawną, ponieważ wykonalne (i powszechne w starszych konfiguracjach) jest skonfigurowanie nazwy tabeli jako innej niż pluralizowana nazwa modelu. Ta odpowiedź daje prawidłową odpowiedź, nawet jeśli konfiguracja odbiega od konfiguracji domyślnej.
— lorefnon

w niektórych przypadkach działa to lepiej niż ActiveRecord::Base.send :subclasses- szukanie nazw tabel jest dobrym pomysłem. Automatyczne generowanie nazw modeli może być problematyczne, jak wspomniano w lorefnon.
— Tilo

.capitalize.singularize.camelizemożna zastąpić .classify.
— Maxim

34

Szukałem sposobów na zrobienie tego i ostatecznie wybrałem ten sposób:

in the controller:
    @data_tables = ActiveRecord::Base.connection.tables

in the view:
  <% @data_tables.each do |dt|  %>
  <br>
  <%= dt %>
  <% end %>
  <br>

źródło: http://portfo.li/rails/348561-how-can-one-list-all-database-tables-from-one-project


1
Tylko w ten sposób mogę uzyskać WSZYSTKIE modele, w tym modele silników Rails używanych w aplikacji. Dzięki za wskazówkę!
— Andrei

2
Kilka przydatnych metod: ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}Niektóre modele mogą nie zostać aktywowane, dlatego musisz je uratować.
— Andrei

2
Dostosowanie @ Andrei jest trochę: model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
— Max Williams

30

Dla Rails5 modeli są teraz podklas stanowi ApplicationRecordwięc do listy wszystkich modeli w swojej aplikacji dostaniesz:

ApplicationRecord.descendants.collect { |type| type.name }

Lub krócej:

ApplicationRecord.descendants.collect(&:name)

Jeśli jesteś w trybie deweloperskim, będziesz musiał chętnie ładować modele przed:

Rails.application.eager_load!

1
Zakładam, że wymagałoby to, aby klasy były już załadowane i dawały niekompletne wyniki w środowisku programistycznym z włączonym automatycznym ładowaniem. Nie będę głosować negatywnie, ale być może należy o tym wspomnieć w odpowiedzi.
— lorefnon

wystarczy, aktualizacja
— Nimir

Jestem na Railsach 6.0.2 i eager_load! nie wykonał metody potomków, aby zwracała tylko pustą tablicę.
— jgomo3

23

Myślę, że rozwiązanie @ hnovick jest fajne, jeśli nie masz modeli bez tabel. To rozwiązanie działałoby również w trybie programistycznym

Moje podejście jest jednak nieco inne -

ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact

klasyfikować jest dobrze podobno daje nazwę klasy z ciągiem prawidłowo . safe_constantize zapewnia, że ​​możesz bezpiecznie przekształcić go w klasę bez zgłaszania wyjątku. Jest to potrzebne, jeśli masz tabele bazy danych, które nie są modelami. kompaktować, aby wszystkie wartości zerowe w wyliczeniu zostały usunięte.


3
To niesamowite @Aditya Sanghi. Nie wiedziałam o tym safe_constantize.
— lightyrs

W przypadku szyn 2.3.x użyj: ActiveRecord :: Base.connection.tables.map {| x | x.classify.constantize rescue nil} .compact
— iheggie

@ iheggie Ogólnie lepiej jest opublikować to jako osobną odpowiedź niż edytować go w istniejącym poście.
— Pokechu22

dzięki, znalazłem odpowiedź, która najbardziej mi odpowiada #adiya
— iluzjonista

21

Jeśli chcesz tylko nazwy klas:

ActiveRecord::Base.descendants.map {|f| puts f}

Po prostu uruchom go w konsoli Rails, nic więcej. Powodzenia!

EDYCJA: @ sj26 ma rację, musisz uruchomić to zanim zaczniesz dzwonić do potomków:

Rails.application.eager_load!

Właśnie tego chciałem. Podziękować!
— sunsations

dzwonisz mapz puts? Nie rozumiem o co chodziActiveRecord::Base.descendants.map(&:model_name)
— Nuno Costa

Możesz to zrobić w ten sposób, ale będą one w jednej tablicy, zamiast linii po linii, w znacznie łatwiejszym do odczytania formacie.
— Jordan Michael Rushing

17

Wydaje mi się, że to działa:

  Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
  @models = Object.subclasses_of(ActiveRecord::Base)

Railsy ładują modele tylko wtedy, gdy są używane, więc linia Dir.glob „wymaga” wszystkich plików w katalogu modeli.

Po umieszczeniu modeli w tablicy możesz zrobić to, o czym myślisz (np. W widoku kodu):

<% @models.each do |v| %>
  <li><%= h v.to_s %></li>
<% end %>

Dzięki, Bhousel. Początkowo stosowałem ten styl, ale ostatecznie skorzystałem z rozwiązania, które Vincent opublikował powyżej, ponieważ oznaczało to, że nie musiałem również „modelować” nazwy pliku (tj. Usuwać dowolne _, wielkie litery! Każdego słowa, a następnie dołączać znowu oni).
— mr_urf

z podkatalogami:...'/app/models/**/*.rb'
— artemave

Object.subclasses_of jest przestarzały po wersji 2.2.8.
— David J.

11

W jednej linii: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }


7
Ten jest fajny, ponieważ w Rails 3 twoje modele nie są domyślnie ładowane automatycznie, więc wiele z powyższych metod nie zwróci wszystkich możliwych modeli. Moja permutacja przechwytuje także modele we wtyczkach i podkatalogach:Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
— wbharding

2
@ wbharding To całkiem miłe, ale popełniają błędy, gdy próbuje ustabilizować nazwy moich testów modelu rspec. ;-)
— Ajedi32,

@wbharding fajne rozwiązanie, ale psuje się, gdy masz modele z przestrzenią nazw
— Marcus Mansur,

10

ActiveRecord::Base.connection.tables


Miłym uzupełnieniem jest także <nazwa_tabeli> .nazwy_kolumn, aby wyświetlić wszystkie kolumny w tabeli. Więc dla swojej tabeli użytkowników wykonałbyś User.column_names
— Mark Locklear

Dzięki temu otrzymasz wszystkie tabele, nie tylko modele, ponieważ niektóre tabele nie zawsze mają powiązane modele.
— courtimas

7

W jednej linii:

 ActiveRecord::Base.subclasses.map(&:name)

2
To nie pokazuje mi wszystkich modeli. Nie pewny dlaczego. W rzeczywistości jest kilka krótkich.
— courtimas

1
pracował dla mnie. „tylko trochę późno, by odpowiedzieć na to wszystko. daj temu czas.
— boulder_ruby

2
Prawdopodobnie potrzebuje Rails.application.eager_load!przed wykonaniem w trybie programowania.
— denis.peplin

7

Nie mogę jeszcze komentować, ale myślę, że odpowiedź sj26 powinna być najwyższą odpowiedzią. Tylko wskazówka:

Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants

6

Z szynami 6 , Zetiwerk stał się ładowarka domyślny kod.

Aby szybko załadować, spróbuj:

Zeitwerk::Loader.eager_load_all

Następnie

ApplicationRecord.descendants

5

Tak, istnieje wiele sposobów na znalezienie wszystkich nazw modeli, ale to, co zrobiłem w moim klejnotu model_info , daje ci wszystkie modele zawarte nawet w klejnotach.

array=[], @model_array=[]
Rails.application.eager_load!
array=ActiveRecord::Base.descendants.collect{|x| x.to_s if x.table_exists?}.compact
array.each do |x|
  if  x.split('::').last.split('_').first != "HABTM"
    @model_array.push(x)
  end
  @model_array.delete('ActiveRecord::SchemaMigration')
end

następnie po prostu wydrukuj to

@model_array

3

Działa to z Railsami 3.2.18

Rails.application.eager_load!

def all_models
  models = Dir["#{Rails.root}/app/models/**/*.rb"].map do |m|
    m.chomp('.rb').camelize.split("::").last
  end
end

upvolt dla tego Rails.application.eager_load! pomysł
— odpowiednik8

3

Aby uniknąć wstępnego ładowania wszystkich Railsów, możesz to zrobić:

Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }

Wymagana_zależność (f) jest taka sama Rails.application.eager_load! używa. Powinno to uniknąć już wymaganych błędów plików.

Następnie możesz użyć wszelkiego rodzaju rozwiązań do listy modeli AR, takich jak ActiveRecord::Base.descendants


2
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }

zgłasza TypeError: brak niejawnej konwersji Symbol na String w konsoli.
— snowangel

1

Oto rozwiązanie, które zostało sprawdzone dzięki złożonej aplikacji Rails (tej, która napędza Square)

def all_models
  # must eager load all the classes...
  Dir.glob("#{RAILS_ROOT}/app/models/**/*.rb") do |model_path|
    begin
      require model_path
    rescue
      # ignore
    end
  end
  # simply return them
  ActiveRecord::Base.send(:subclasses)
end

Pobiera najlepsze części odpowiedzi w tym wątku i łączy je w najprostsze i najdokładniejsze rozwiązanie. To obsługuje przypadki, w których twoje modele znajdują się w podkatalogach, użyj set_table_name itp.


1

Właśnie natknąłem się na ten, ponieważ muszę wydrukować wszystkie modele z ich atrybutami (zbudowane na komentarzu @Aditya Sanghi):

ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact.each{ |model| print "\n\n"+model.name; model.new.attributes.each{|a,b| print "\n#{a}"}}

1

To zadziałało dla mnie. Specjalne podziękowania dla wszystkich powyższych postów. Powinno to zwrócić kolekcję wszystkich twoich modeli.

models = []

Dir.glob("#{Rails.root}/app/models/**/*.rb") do |model_path|
  temp = model_path.split(/\/models\//)
  models.push temp.last.gsub(/\.rb$/, '').camelize.constantize rescue nil
end

1

Do Railsrealizuje sposób descendants, ale modele niekoniecznie zawsze dziedziczy od ActiveRecord::Base, na przykład, klasy, która zawiera modułActiveModel::Model będzie miał takie samo zachowanie jako model, po prostu nie będzie powiązana z tabelą.

Uzupełniając to, co mówią koledzy powyżej, najmniejszy wysiłek przyniesie to:

Łatka Małpy klasy ClassRuby:

class Class
  def extends? constant
    ancestors.include?(constant) if constant != self
  end
end

oraz metodę models, w tym przodków, ponieważ:

Metoda Module.constantszwraca (powierzchownie) kolekcję symbolszamiast stałych, więc metodę Array#selectmożna zastąpić tak jak ta łatka małpy Module:

class Module

  def demodulize
    splitted_trail = self.to_s.split("::")
    constant = splitted_trail.last

    const_get(constant) if defines?(constant)
  end
  private :demodulize

  def defines? constant, verbose=false
    splitted_trail = constant.split("::")
    trail_name = splitted_trail.first

    begin
      trail = const_get(trail_name) if Object.send(:const_defined?, trail_name)
      splitted_trail.slice(1, splitted_trail.length - 1).each do |constant_name|
        trail = trail.send(:const_defined?, constant_name) ? trail.const_get(constant_name) : nil
      end
      true if trail
    rescue Exception => e
      $stderr.puts "Exception recovered when trying to check if the constant \"#{constant}\" is defined: #{e}" if verbose
    end unless constant.empty?
  end

  def has_constants?
    true if constants.any?
  end

  def nestings counted=[], &block
    trail = self.to_s
    collected = []
    recursivityQueue = []

    constants.each do |const_name|
      const_name = const_name.to_s
      const_for_try = "#{trail}::#{const_name}"
      constant = const_for_try.constantize

      begin
        constant_sym = constant.to_s.to_sym
        if constant && !counted.include?(constant_sym)
          counted << constant_sym
          if (constant.is_a?(Module) || constant.is_a?(Class))
            value = block_given? ? block.call(constant) : constant
            collected << value if value

            recursivityQueue.push({
              constant: constant,
              counted: counted,
              block: block
            }) if constant.has_constants?
          end
        end
      rescue Exception
      end

    end

    recursivityQueue.each do |data|
      collected.concat data[:constant].nestings(data[:counted], &data[:block])
    end

    collected
  end

end

Łatka małpy String.

class String
  def constantize
    if Module.defines?(self)
      Module.const_get self
    else
      demodulized = self.split("::").last
      Module.const_get(demodulized) if Module.defines?(demodulized)
    end
  end
end

I wreszcie metoda modeli

def models
  # preload only models
  application.config.eager_load_paths = model_eager_load_paths
  application.eager_load!

  models = Module.nestings do |const|
    const if const.is_a?(Class) && const != ActiveRecord::SchemaMigration && (const.extends?(ActiveRecord::Base) || const.include?(ActiveModel::Model))
  end
end

private

  def application
    ::Rails.application
  end

  def model_eager_load_paths
    eager_load_paths = application.config.eager_load_paths.collect do |eager_load_path|
      model_paths = application.config.paths["app/models"].collect do |model_path|
        eager_load_path if Regexp.new("(#{model_path})$").match(eager_load_path)
      end
    end.flatten.compact
  end

1
Dir.foreach("#{Rails.root.to_s}/app/models") do |model_path|
  next unless model_path.match(/.rb$/)
  model_class = model_path.gsub(/.rb$/, '').classify.constantize
  puts model_class
end

To da ci wszystkie klasy modeli, które masz w swoim projekcie.


0
def load_models_in_development
  if Rails.env == "development"
    load_models_for(Rails.root)
    Rails.application.railties.engines.each do |r|
      load_models_for(r.root)
    end
  end
end

def load_models_for(root)
  Dir.glob("#{root}/app/models/**/*.rb") do |model_path|
    begin
      require model_path
    rescue
      # ignore
    end
  end
end

0

Próbowałem wielu z tych odpowiedzi bezskutecznie w Rails 4 (wow, zmienili jedną lub dwie rzeczy na litość boską) postanowiłem dodać własną. Te, które wywołały ActiveRecord :: Base.connection i wyciągnęły nazwy tabel, działały, ale nie uzyskały pożądanego rezultatu, ponieważ ukryłem niektóre modele (w folderze wewnątrz aplikacji / models /), których nie chciałem usunąć:

def list_models
  Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end

Umieszczam to w inicjalizatorze i mogę wywoływać to z dowolnego miejsca. Zapobiega niepotrzebnemu użyciu myszy.


0

mogę to sprawdzić

@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}

0

Zakładając, że wszystkie modele są w aplikacji / modelach i masz grep i awk na swoim serwerze (większość przypadków),

# extract lines that match specific string, and print 2nd word of each line
results = `grep -r "< ActiveRecord::Base" app/models/ | awk '{print $2}'`
model_names = results.split("\n")

Jest to szybsze niż Rails.application.eager_load!lub zapętlanie każdego pliku za pomocą Dir.

EDYTOWAĆ:

Wadą tej metody jest brak modeli, które pośrednio dziedziczą z ActiveRecord (np FictionalBook < Book.). Najpewniejszym sposobem jest to Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name), że jest trochę powolne.


0

Rzucam ten przykład tutaj, jeśli ktoś uzna to za przydatne. Rozwiązanie oparte jest na tej odpowiedzi https://stackoverflow.com/a/10712838/473040 .

Powiedzmy, że masz kolumnę public_uid, który jest używany jako podstawowy identyfikator światem zewnętrznym (można findjreasons dlaczego chciał to zrobić tutaj )

Powiedzmy teraz, że wprowadziłeś to pole do wielu istniejących modeli i teraz chcesz zregenerować wszystkie rekordy, które nie zostały jeszcze ustawione. Możesz to zrobić w ten sposób

# lib/tasks/data_integirity.rake
namespace :di do
  namespace :public_uids do
    desc "Data Integrity: genereate public_uid for any model record that doesn't have value of public_uid"
    task generate: :environment do
      Rails.application.eager_load!
      ActiveRecord::Base
        .descendants
        .select {|f| f.attribute_names.include?("public_uid") }
        .each do |m| 
          m.where(public_uid: nil).each { |mi| puts "Generating public_uid for #{m}#id #{mi.id}"; mi.generate_public_uid; mi.save }
      end 
    end 
  end 
end

możesz teraz biegać rake di:public_uids:generate

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.