Gibt es eine Möglichkeit, eine Sammlung aller Modelle in Ihrer Rails-App abzurufen?
Grundsätzlich kann ich Folgendes tun: -
Models.each do |model|
puts model.class.name
end
Gibt es eine Möglichkeit, eine Sammlung aller Modelle in Ihrer Rails-App abzurufen?
Grundsätzlich kann ich Folgendes tun: -
Models.each do |model|
puts model.class.name
end
Antworten:
EDIT: Schauen Sie sich die Kommentare und andere Antworten an. Es gibt klügere Antworten als diese! Oder versuchen Sie, dieses als Community-Wiki zu verbessern.
Modelle registrieren sich nicht bei einem Master-Objekt. Nein, Rails verfügt nicht über die Liste der Modelle.
Sie können aber trotzdem im Inhalt des Modellverzeichnisses Ihrer Anwendung nachsehen ...
Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end
BEARBEITEN: Eine andere (wilde) Idee wäre, Ruby Reflection zu verwenden, um nach allen Klassen zu suchen, die ActiveRecord :: Base erweitern. Ich weiß nicht, wie Sie alle Klassen auflisten können ...
EDIT: Nur zum Spaß habe ich einen Weg gefunden, alle Klassen aufzulisten
Module.constants.select { |c| (eval c).is_a? Class }
BEARBEITEN: Endlich gelang es, alle Modelle aufzulisten, ohne die Verzeichnisse zu betrachten
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
Wenn Sie auch abgeleitete Klassen verarbeiten möchten, müssen Sie die gesamte Oberklassenkette testen. Ich habe dazu der Class-Klasse eine Methode hinzugefügt:
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
ist nicht mehr in Rails 3 verfügbar. Verwenden Dir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
jetzt. Wenn Sie also alle Modelle laden möchten, können Sie sie leicht iterieren - siehe meine Antwort unten.
Die ganze Antwort für die Schienen 3, 4 und 5 lautet:
Wenn deaktiviert cache_classes
ist (standardmäßig ist es in der Entwicklung deaktiviert, in der Produktion jedoch aktiviert):
Rails.application.eager_load!
Dann:
ActiveRecord::Base.descendants
Auf diese Weise wird sichergestellt, dass alle Modelle in Ihrer Anwendung geladen werden, unabhängig davon, wo sie sich befinden, und dass alle von Ihnen verwendeten Edelsteine, die Modelle bereitstellen, ebenfalls geladen werden.
Dies sollte auch für Klassen funktionieren ActiveRecord::Base
, die wie ApplicationRecord
in Rails 5 erben und nur den Teilbaum der Nachkommen zurückgeben:
ApplicationRecord.descendants
Wenn Sie möchten mehr darüber wissen , wie dies geschehen ist, überprüfen Active :: DescendantsTracker .
:environment
für die eager_load!
an der Arbeit.
Rails.application.eager_load!
, können Sie einfach die Modelle laden:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
Verzeichnisse glob . Das eifrige Laden der gesamten Anwendung ist eine vollständigere Antwort und stellt sicher, dass für die Definition von Modellen absolut nichts mehr übrig ist.
Rails.application.paths["app/models"].eager_load!
Nur für den Fall, dass jemand über diese stolpert, habe ich eine andere Lösung, ohne mich auf das direkte Lesen oder Erweitern der Klassenklasse zu verlassen ...
ActiveRecord::Base.send :subclasses
Dies gibt ein Array von Klassen zurück. So können Sie dann tun
ActiveRecord::Base.send(:subclasses).map(&:name)
ActiveRecord::Base.subclasses
aber benutzen send
? Es scheint auch so, als müssten Sie das Modell "berühren", bevor es beispielsweise c = Category.new
angezeigt wird und angezeigt wird. Sonst wird es nicht.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
sie aufgelistet werden.
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
wird zurückkehren
["Article", "MenuItem", "Post", "ZebraStripePerson"]
Zusätzliche Informationen Wenn Sie eine Methode für den Objektnamen ohne Modell aufrufen möchten: Zeichenfolge unbekannte Methode oder Variablenfehler verwenden Sie diese
model.classify.constantize.attribute_names
ActiveRecord::Base.send :subclasses
- das Suchen nach Tabellennamen ist eine gute Idee. Das automatische Generieren der Modellnamen kann problematisch sein, wie bereits erwähnt.
.capitalize.singularize.camelize
kann ersetzt werden .classify
.
Ich suchte nach Möglichkeiten, dies zu tun, und entschied mich schließlich für diesen Weg:
in the controller:
@data_tables = ActiveRecord::Base.connection.tables
in the view:
<% @data_tables.each do |dt| %>
<br>
<%= dt %>
<% end %>
<br>
Quelle: 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}
Einige Modelle sind möglicherweise nicht aktiviert, daher müssen Sie sie retten.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Für Rails5 sind Modelle jetzt Unterklassen von ApplicationRecord
, um eine Liste aller Modelle in Ihrer App zu erhalten, die Sie ausführen :
ApplicationRecord.descendants.collect { |type| type.name }
Oder kürzer:
ApplicationRecord.descendants.collect(&:name)
Wenn Sie sich im Entwicklungsmodus befinden, müssen Sie Modelle unbedingt laden, bevor Sie:
Rails.application.eager_load!
Ich denke, die Lösung von @ hnovick ist cool, wenn Sie keine Modelle ohne Tisch haben. Diese Lösung würde auch im Entwicklungsmodus funktionieren
Mein Ansatz ist jedoch subtil anders -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
classify soll Ihnen den Namen der Klasse aus einer Zeichenfolge richtig geben . safe_constantize stellt sicher, dass Sie es sicher in eine Klasse verwandeln können, ohne eine Ausnahme auszulösen. Dies ist erforderlich, wenn Sie Datenbanktabellen haben, die keine Modelle sind. kompakt, so dass alle Nullen in der Aufzählung entfernt werden.
safe_constantize
.
Wenn Sie nur die Klassennamen möchten:
ActiveRecord::Base.descendants.map {|f| puts f}
Führen Sie es einfach in der Rails-Konsole aus, sonst nichts. Viel Glück!
EDIT: @ sj26 ist richtig, Sie müssen dies zuerst ausführen, bevor Sie Nachkommen aufrufen können:
Rails.application.eager_load!
map
mit puts
? Ich verstehe nicht, was der Punkt sein sollteActiveRecord::Base.descendants.map(&:model_name)
Das scheint bei mir zu funktionieren:
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
Rails lädt Modelle nur, wenn sie verwendet werden. Daher "benötigt" die Dir.glob-Zeile alle Dateien im Modellverzeichnis.
Sobald Sie die Modelle in einem Array haben, können Sie das tun, was Sie gedacht haben (z. B. im Ansichtscode):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
...'/app/models/**/*.rb'
In einer Zeile: 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
In nur einer Zeile:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
vor der Ausführung im Entwicklungsmodus benötigt.
Ich kann noch keinen Kommentar abgeben , aber ich denke, die Antwort von sj26 sollte die beste Antwort sein. Nur ein Hinweis:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
Ja, es gibt viele Möglichkeiten, wie Sie alle Modellnamen finden können, aber was ich in meinem Gem model_info getan habe, ist, dass Sie alle Modelle erhalten, die sogar in den Gems enthalten sind.
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
dann einfach ausdrucken
@model_array
Dies funktioniert für Rails 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
Um zu vermeiden, dass alle Rails vorgeladen werden, können Sie Folgendes tun:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency (f) ist das gleiche, das Rails.application.eager_load!
verwendet wird. Dies sollte bereits erforderliche Dateifehler vermeiden.
Dann können Sie alle Arten von Lösungen verwenden, um AR-Modelle aufzulisten, wie z ActiveRecord::Base.descendants
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
Hier ist eine Lösung, die mit einer komplexen Rails-App (der Powering Square) überprüft wurde.
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
Es nimmt die besten Teile der Antworten in diesem Thread und kombiniert sie in der einfachsten und gründlichsten Lösung. Dies behandelt Fälle, in denen sich Ihre Modelle in Unterverzeichnissen befinden, verwenden Sie set_table_name usw.
Ich bin gerade auf dieses Modell gestoßen, da ich alle Modelle mit ihren Attributen drucken muss (basierend auf dem Kommentar von @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}"}}
Das hat bei mir funktioniert. Besonderer Dank geht an alle oben genannten Beiträge. Dies sollte eine Sammlung aller Ihrer Modelle zurückgeben.
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
Das Rails
implementiert die Methode descendants
, aber Modelle erben nicht unbedingt jemals von ActiveRecord::Base
beispielsweise der Klasse, die das Modul enthältActiveModel::Model
, haben das gleiche Verhalten wie ein Modell, werden jedoch nicht mit einer Tabelle verknüpft.
Wenn man also die obigen Aussagen ergänzt, würde dies die geringste Anstrengung bewirken:
Affenfleck der Klasse Class
des Rubins:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
und die Methode models
, einschließlich der Vorfahren, wie folgt:
Die Methode Module.constants
gibt (oberflächlich) eine Sammlung von symbols
Konstanten anstelle von Konstanten zurück, sodass die Methode Array#select
wie folgt ersetzt werden kann 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
Affenbeet von 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
Und schließlich die Modellmethode
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
Ich habe so viele dieser Antworten in Rails 4 erfolglos ausprobiert (wow, sie haben ein oder zwei Dinge geändert, um Himmels willen), dass ich beschlossen habe, meine eigenen hinzuzufügen. Diejenigen, die ActiveRecord :: Base.connection aufgerufen und die Tabellennamen abgerufen haben, haben funktioniert, aber nicht das gewünschte Ergebnis erzielt, da ich einige Modelle (in einem Ordner in app / models /) versteckt habe, die ich nicht wollte löschen:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
Ich habe das in einen Initialisierer gesteckt und kann es von überall aufrufen. Verhindert unnötigen Mausgebrauch.
Angenommen, alle Modelle befinden sich in App / Modellen und Sie haben grep & awk auf Ihrem Server (in den meisten Fällen).
# 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")
Es ist schneller als Rails.application.eager_load!
oder durchläuft jede Datei mit Dir
.
BEARBEITEN:
Der Nachteil dieser Methode besteht darin, dass Modelle fehlen, die indirekt von ActiveRecord erben (z FictionalBook < Book
. B. ). Der sicherste Weg ist Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)
, obwohl es ein bisschen langsam ist.
Ich werfe dieses Beispiel nur hier, wenn jemand es nützlich findet. Die Lösung basiert auf dieser Antwort https://stackoverflow.com/a/10712838/473040 .
Angenommen, Sie haben eine Spalte public_uid
, die als primäre ID für die Außenwelt verwendet wird (Gründe, warum Sie dies tun möchten, finden Sie hier ).
Angenommen, Sie haben dieses Feld in einer Reihe vorhandener Modelle eingeführt und möchten nun alle noch nicht festgelegten Datensätze neu generieren. Sie können das so machen
# 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
Sie können jetzt laufen rake di:public_uids:generate