Czy istnieje sposób, aby uzyskać kolekcję wszystkich modeli w aplikacji Rails?
Zasadniczo mogę zrobić:
Models.each do |model|
puts model.class.name
end
Czy istnieje sposób, aby uzyskać kolekcję wszystkich modeli w aplikacji Rails?
Zasadniczo mogę zrobić:
Models.each do |model|
puts model.class.name
end
Odpowiedzi:
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
RAILS_ROOT
nie jest już dostępny w Rails 3. Zamiast tego użyjDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
teraz, więc jeśli chcesz załadować wszystkie modele, możesz je łatwo iterować - zobacz moją odpowiedź poniżej.
Cała odpowiedź dla szyn 3, 4 i 5 brzmi:
Jeśli cache_classes
jest 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 ApplicationRecord
w 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 .
:environment
dla eager_load!
do pracy.
Rails.application.eager_load!
, możesz po prostu załadować modele:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
katalogi. Chętne ładowanie całej aplikacji jest bardziej kompletną odpowiedzią i upewni się, że absolutnie nie ma już nic do zdefiniowania modeli.
Rails.application.paths["app/models"].eager_load!
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)
ActiveRecord::Base.subclasses
ale musisz używać send
? Wygląda też na to, że musisz „dotknąć” modelu, zanim się pojawi, na przykład c = Category.new
i się pojawi. W przeciwnym razie nie będzie.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
je wypisze.
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
ActiveRecord::Base.send :subclasses
- szukanie nazw tabel jest dobrym pomysłem. Automatyczne generowanie nazw modeli może być problematyczne, jak wspomniano w lorefnon.
.capitalize.singularize.camelize
można zastąpić .classify
.
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
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ć.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Dla Rails5 modeli są teraz podklas stanowi ApplicationRecord
wię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!
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.
safe_constantize
.
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!
map
z puts
? Nie rozumiem o co chodziActiveRecord::Base.descendants.map(&:model_name)
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 %>
...'/app/models/**/*.rb'
W jednej linii: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
ActiveRecord::Base.connection.tables
W jednej linii:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
przed wykonaniem w trybie programowania.
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
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
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
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
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.
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}"}}
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
Do Rails
realizuje 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 Class
Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
oraz metodę models
, w tym przodków, ponieważ:
Metoda Module.constants
zwraca (powierzchownie) kolekcję symbols
zamiast stałych, więc metodę Array#select
moż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
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
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.
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.
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