wake-up-neo.com

Gibt es eine Möglichkeit, eine Sammlung aller Modelle in Ihrer Rails-App abzurufen?

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
181
mr_urf

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
91
Vincent Robert

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 .

353
sj26

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)
112
kikito
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
63
lightyrs

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

31
jaime

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.

22
Aditya Sanghi

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!
20
Nimir

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 %>
17
bhousel

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

11
vjt

ActiveRecord::Base.connection.tables

8
Mark Locklear

In nur einer Zeile:

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

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
6
panteo

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
4
nitanshu verma

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
3
ryan0

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.

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

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
1
Kevin

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
1
rplaurindo

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.

1

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}"}}
1
gouravtiwari21

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.

0
konyak

kann das überprüfen 

@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
0
Arvind
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.

0
Victor
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
Abdul

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.

0
boulder_ruby

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.

0
equivalent8