Gibt es eine Möglichkeit, eine Sammlung aller Modelle in Ihrer Rails-App zu erhalten?
Kann ich grundsätzlich folgendes machen: -
Models.each do |model|
puts model.class.name
end
EDIT: Schau dir die Kommentare und anderen Antworten an. Es gibt klügere Antworten als diese! Oder versuchen Sie, dieses als Community-Wiki zu verbessern.
Modelle registrieren sich nicht selbst bei einem Master-Objekt, daher hat Rails keine Modellliste.
Sie könnten jedoch immer noch in den Inhalt des Modellverzeichnisses Ihrer Anwendung schauen ...
Dir.foreach("#{Rails_ROOT}/app/models") do |model_path|
# ...
end
EDIT: Eine andere (wilde) Idee wäre, Ruby-Reflektion zu verwenden, um nach allen Klassen zu suchen, die ActiveRecord :: Base erweitern. Ich weiß nicht, wie man alle Klassen auflisten kann ...
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 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 mit abgeleiteten Klassen arbeiten möchten, müssen Sie die gesamte Superklassenkette testen. Ich habe es getan, indem ich der Klasse class eine Methode hinzugefügt habe:
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
Die vollständige Antwort für Rails 3, 4 und 5 lautet:
Wenn cache_classes
ausgeschaltet ist (standardmäßig in der Entwicklung, aber in der Produktion):
Rails.application.eager_load!
Dann:
ActiveRecord::Base.descendants
Dadurch wird sichergestellt, dass alle Modelle in Ihrer Anwendung, unabhängig davon, wo sie sich befinden, geladen werden. Außerdem werden alle von Ihnen verwendeten Edelsteine, deren Modelle bereitgestellt werden, geladen.
Dies sollte auch für Klassen funktionieren, die von ActiveRecord::Base
erben, wie ApplicationRecord
in Rails 5, und nur den Teilbaum der Nachkommen zurückgeben:
ApplicationRecord.descendants
Wenn Sie mehr über erfahren möchten, wie dies geschieht, lesen Sie ActiveSupport :: DescendantsTracker .
Nur für den Fall, dass irgendjemand auf diesen einen stößt, habe ich eine andere Lösung, nicht auf das Lesen oder Erweitern der Klasse ...
ActiveRecord::Base.send :subclasses
Dies gibt ein Array von Klassen zurück. Das kannst du dann tun
ActiveRecord::Base.send(:subclasses).map(&:name)
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 Option
model.classify.constantize.attribute_names
Ich suchte nach Wegen, 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>
source: http://portfo.li/Rails/348561-wie-can-one-list-all-database-tables-from-one-project
Ich denke, @ hnovicks Lösung ist eine coole Lösung, wenn Sie keine Modelle ohne Tisch haben. Diese Lösung funktioniert auch im Entwicklungsmodus
Mein Ansatz ist jedoch subtil anders -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
classify soll Ihnen wohl den Namen der Klasse aus einem String richtig geben. safe_constantize stellt sicher, dass Sie die Klasse sicher in eine Klasse umwandeln können, ohne eine Ausnahme auszulösen. Dies ist erforderlich, wenn Sie Datenbanktabellen haben, die keine Modelle sind. kompakt, so dass alle Nils in der Aufzählung entfernt werden.
Wenn Sie nur die Klassennamen wünschen:
ActiveRecord::Base.descendants.map {|f| puts f}
Führen Sie es einfach in der Rails-Konsole aus, mehr nicht. Viel Glück!
BEARBEITEN: @ sj26 ist richtig, Sie müssen dies zuerst ausführen, bevor Sie Nachkommen aufrufen können:
Rails.application.eager_load!
Für Rails5 -Modelle sind jetzt Unterklassen von ApplicationRecord
, um eine Liste aller Modelle in Ihrer App zu erhalten:
ApplicationRecord.descendants.collect { |type| type.name }
Oder kürzer:
ApplicationRecord.descendants.collect(&:name)
Wenn Sie sich im Dev-Modus befinden, müssen Sie eifrig Modelle laden, bevor Sie:
Rails.application.eager_load!
Das scheint für mich zu funktionieren:
Dir.glob(Rails_ROOT + '/app/models/*.rb').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
Rails lädt nur Modelle, wenn sie verwendet werden, so dass die Dir.glob-Zeile alle Dateien im Verzeichnis models benötigt.
Sobald Sie die Modelle in einem Array haben, können Sie tun, was Sie denken (z. B. in Ansichtscode):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
In einer Zeile: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
ActiveRecord::Base.connection.tables
In nur einer Zeile:
ActiveRecord::Base.subclasses.map(&:name)
Ich kann noch nicht kommentieren, aber ich denke, sj26 Antwort 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, alle Modellnamen zu finden, aber was ich in meinem gem model_info getan habe, gibt Ihnen alle Modelle, die sogar in den Edelsteinen 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 vorab geladen werden, können Sie Folgendes tun:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
requirement_dependency (f) ist die gleiche, die Rails.application.eager_load!
verwendet. Dies sollte bereits erforderliche Dateifehler vermeiden.
Dann können Sie alle möglichen Lösungen verwenden, um AR-Modelle aufzulisten, z. B. ActiveRecord::Base.descendants
.
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
Das hat bei mir funktioniert. Besonderer Dank geht an alle Beiträge oben. 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
Die Variable Rails
implementiert die Methode descendants
. Modelle erben jedoch nicht immer ActiveRecord::Base
. Die Klasse, die das Modul ActiveModel::Model
enthält, verhält sich wie ein Modell, wird jedoch nicht mit einer Tabelle verknüpft.
In Ergänzung zu dem, was die Kollegen oben sagen, würde der geringste Aufwand dies tun:
Monkey Patch der Klasse Class
des Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
und die Methode models
, einschließlich Vorfahren, wie folgt:
Die Methode Module.constants
gibt (oberflächlich) eine Auflistung von symbols
anstelle von Konstanten zurück. Die Methode Array#select
kann daher wie dieses Affen-Patch der Module
ersetzt werden:
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
Affen-Patch 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 Models-Methode
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
Hier ist eine Lösung, die mit einer komplexen Rails-App geprüft wurde (der One Powering 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
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 gestoßen, da ich alle Modelle mit ihren Attributen drucken muss (basierend auf @Aditya Sanghis Kommentar):
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}"}}
Angenommen, alle Modelle sind in App/Modellen und Sie haben grep & awk auf Ihrem Server (in der Mehrzahl der Fälle).
# 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 durchlaufen jede Datei mit Dir
.
BEARBEITEN:
Der Nachteil dieser Methode besteht darin, dass Modelle fehlen, die indirekt von ActiveRecord (z. B. FictionalBook < Book
) erben. Der sicherste Weg ist Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)
, auch wenn es etwas langsam ist.
kann das überprüfen
@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
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
Dadurch erhalten Sie alle Modellklassen, die Sie in Ihrem Projekt haben.
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 erfolglos in Rails 4 ausprobiert (wow, dass sie ein oder zwei Dinge geändert haben), entschied ich mich, meine eigenen hinzuzufügen. Diejenigen, die ActiveRecord :: Base.connection aufgerufen und die Tabellennamen abgerufen haben, funktionierten, erzielten jedoch nicht das gewünschte Ergebnis, da ich einige Modelle (in einem Ordner innerhalb von app/models /) versteckte, 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 stelle das in einen Initialisierer und kann es von überall her aufrufen. Verhindert unnötige Mausnutzung.
Ich werfe dieses Beispiel nur hier, wenn jemand es für nützlich hält. 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 (Sie können Hinweise finden, warum Sie dies tun möchten hier ).
Nehmen wir an, Sie haben dieses Feld in eine Reihe vorhandener Modelle eingeführt und möchten jetzt alle Datensätze neu generieren, die noch nicht festgelegt sind. Das kannst du 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 rake di:public_uids:generate
ausführen.