C'è un modo per ottenere una raccolta di tutti i modelli nella tua app Rails?


201

C'è un modo per ottenere una raccolta di tutti i modelli nella tua app Rails?

Fondamentalmente, posso fare cose come: -

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

1
Se hai bisogno di raccogliere tutti i modelli, compresi i modelli di motori / ferrovie Rails, vedi la risposta di @jaime
Andrei,

Non funziona su binari 5.1
ak

Risposte:


98

MODIFICA: guarda i commenti e le altre risposte. Ci sono risposte più intelligenti di questa! O prova a migliorare questo come wiki della community.

I modelli non si registrano su un oggetto master, quindi no, Rails non ha l'elenco dei modelli.

Ma potresti comunque cercare nel contenuto della directory dei modelli della tua applicazione ...

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

EDIT: Un'altra idea (selvaggia) sarebbe quella di utilizzare la riflessione Ruby per cercare tutte le classi che estendono ActiveRecord :: Base. Non so come puoi elencare tutte le classi però ...

EDIT: Solo per divertimento, ho trovato un modo per elencare tutte le classi

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

EDIT: finalmente riuscito a elencare tutti i modelli senza guardare le directory

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

Se si desidera gestire anche la classe derivata, sarà necessario testare l'intera catena di superclassi. L'ho fatto aggiungendo un metodo alla classe 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
Cordiali saluti, ho cronometrato entrambi i metodi solo per divertimento. Cercare le directory è un ordine di grandezza più veloce della ricerca nelle classi. Probabilmente era ovvio, ma ora lo sai :)
Edward Anderson,

9
Inoltre, è importante notare che la ricerca di modelli tramite i metodi delle costanti non includerà tutto ciò a cui non è stato fatto riferimento dall'avvio dell'app, poiché carica solo i modelli su richiesta.
Edward Anderson,

4
Preferisco "Kernel.const_get constant_name" a "eval constant_name".
Jeremy Weathers,

3
RAILS_ROOTnon è più disponibile in Rails 3. Usa inveceDir.glob(Rails.root.join('app/models/*'))
fanaugen

1
In realtà, i modelli si registrano come discendenti di ActiveRecord::Baseadesso, quindi se desideri caricare tutti i modelli, puoi iterarli facilmente — vedi la mia risposta di seguito.
sj26,

393

L'intera risposta per Rails 3, 4 e 5 è:

Se cache_classesè disattivato (per impostazione predefinita è disattivato in fase di sviluppo, ma è attivo in produzione):

Rails.application.eager_load!

Poi:

ActiveRecord::Base.descendants

Questo assicura che tutti i modelli nella tua applicazione, indipendentemente da dove si trovino, siano caricati e tutte le gemme che stai utilizzando che forniscono anche i modelli sono caricati.

Questo dovrebbe funzionare anche su classi che ereditano ActiveRecord::Base, come ApplicationRecordin Rails 5, e restituire solo quella sottostruttura di discendenti:

ApplicationRecord.descendants

Se vuoi sapere di più su come farlo, dai un'occhiata a ActiveSupport :: DescendantsTracker .


33
Eccezionale! Questa dovrebbe essere la risposta accettata. Per chiunque lo usi in un'attività di rake: fai in modo che l'attività dipenda :environmentdal fatto eager_load!che funzioni.
Jo Liss,

1
Oppure, in alternativa leggermente più veloce Rails.application.eager_load!, puoi semplicemente caricare i modelli:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Ajedi32

5
@ Ajedi32 che non è completo, i modelli possono essere definiti al di fuori di quelle directory, specialmente quando si usano motori con modelli. Leggermente meglio, almeno glob tutte le Rails.paths["app/models"].existentdirectory. Il caricamento desideroso dell'intera applicazione è una risposta più completa e farà in modo che non ci sia assolutamente alcun posto per definire i modelli.
sabato

2
Ho capito cosa significa sj26 ma forse c'è un piccolo errore: per quanto ne so nell'ambiente di sviluppo cache_classes è disattivato (falso) ecco perché è necessario caricare manualmente l'applicazione per accedere a tutti i modelli. spiegato qui
masciugo,

3
@ Ajedi32 di nuovo, non la risposta completa. Se vuoi caricare solo i modelli, prova:Rails.application.paths["app/models"].eager_load!
sj26

119

Nel caso in cui qualcuno si imbatta in questo, ho un'altra soluzione, non fare affidamento sulla lettura di dir o sull'estensione della classe Class ...

ActiveRecord::Base.send :subclasses

Ciò restituirà una matrice di classi. Quindi puoi farlo

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

8
perché non usi ActiveRecord::Base.subclassesma devi usare send? Inoltre, sembra che devi "toccare" il modello prima che venga visualizzato, ad esempio c = Category.newe verrà visualizzato. Altrimenti no.
polarità,

52
In Rails 3, questo è stato modificato inActiveRecord::Base.descendants
Tobias Cohen,

3
Devi usare "invia" perché il membro: sottoclassi è protetto.
Kevin Rood,

11
Grazie per il consiglio di Rails 3. Per chiunque venga, devi comunque "toccare" i modelli prima di ActiveRecord::Base.descendantselencarli.
nfm,

3
Tecnicamente in Rails 3 hai sottoclassi e discendenti, significano cose diverse.
sj26

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

sarà di ritorno

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

Ulteriori informazioni Se si desidera chiamare un metodo sul nome dell'oggetto senza modello: utilizzare un metodo stringa sconosciuto o errori variabili

model.classify.constantize.attribute_names

8
Questo ti porterà tutti i tavoli, non solo i modelli, poiché alcuni tavoli non hanno sempre modelli associati.
cortei

Questa risposta deve essere considerata errata in quanto è possibile (e comune nelle configurazioni legacy) configurare il nome della tabella in modo che sia diverso dal nome pluralizzato del modello. Questa risposta fornisce la risposta corretta anche quando l'installazione si discosta dalla configurazione predefinita.
lorefnon,

in alcuni casi funziona meglio di ActiveRecord::Base.send :subclasses: cercare i nomi delle tabelle è una buona idea. La generazione automatica dei nomi dei modelli potrebbe essere problematica come menzionato da lorefnon.
Tilo,

.capitalize.singularize.camelizepuò essere sostituito a .classify.
Massima

34

Ho cercato modi per farlo e ho finito per scegliere in questo modo:

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

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

fonte: http://portfo.li/rails/348561-how-can-one-list-all-database-tables-from-one-project


1
Questo è l'unico modo per ottenere TUTTI i modelli, inclusi i modelli di motori Rails utilizzati nell'app. Grazie per il consiglio!
Andrei,

2
Alcuni metodi utili: ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}alcuni dei modelli potrebbero non essere attivati, quindi è necessario salvarlo.
Andrei,

2
Adattando @ Andrei è un po ': model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Max Williams,

30

Per Rails5 i modelli sono ora sottoclassi di ApplicationRecordcosì per ottenere l'elenco di tutti i modelli nella tua app che fai:

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

O più breve:

ApplicationRecord.descendants.collect(&:name)

Se sei in modalità dev, dovrai prima caricare i modelli di caricamento:

Rails.application.eager_load!

1
Presumo che ciò richiederebbe che le classi siano già state caricate e darebbe risultati incompleti nell'ambiente di sviluppo con il caricamento automatico abilitato. Non voglio sottovalutare, ma forse questo dovrebbe essere menzionato nella risposta.
lorefnon,

tariffa sufficiente, aggiornamento
Nimir

Sono su Rails 6.0.2 e il eager_load! non ha fatto il metodo dei discendenti per restituire nient'altro che un array vuoto.
jgomo3,

23

Penso che la soluzione di @ hnovick sia interessante se non hai modelli senza tavolo. Questa soluzione funzionerebbe anche in modalità di sviluppo

Il mio approccio è leggermente diverso però -

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

si suppone che classify ti dia correttamente il nome della classe da una stringa . safe_constantize ti consente di trasformarlo in una classe in sicurezza senza generare un'eccezione. Ciò è necessario nel caso in cui siano presenti tabelle di database che non sono modelli. compatta in modo che tutti gli zero nell'enumerazione vengano rimossi.


3
È fantastico @Aditya Sanghi. Non lo sapevo safe_constantize.
Lightyrs,

Per le rotaie 2.3.x, utilizzare: ActiveRecord :: Base.connection.tables.map {| x | x.classify.constantize rescue nil} .compact
iheggie

@iheggie In genere è meglio pubblicarlo come risposta separata piuttosto che modificarlo nel post esistente.
Pokechu22,

grazie, ho trovato la risposta più adatta a me #adiya
illusionista,

21

Se vuoi solo i nomi delle classi:

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

Basta eseguirlo nella console di Rails, niente di più. In bocca al lupo!

EDIT: @ sj26 ha ragione, devi eseguirlo prima di poter chiamare i discendenti:

Rails.application.eager_load!

Proprio quello che volevo. Grazie!
sunsations,

chiamando mapcon puts? Non capisco il punto dovrebbe essereActiveRecord::Base.descendants.map(&:model_name)
Nuno Costa,

Puoi farlo in questo modo, ma saranno in un singolo array, anziché riga per riga, in un formato molto più facile da leggere.
Jordan Michael Rushing,

17

Questo sembra funzionare per me:

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

Rails carica i modelli solo quando vengono utilizzati, quindi la linea Dir.glob "richiede" tutti i file nella directory dei modelli.

Una volta che hai i modelli in un array, puoi fare quello che stavi pensando (ad esempio nel codice di visualizzazione):

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

Grazie bhousel. Inizialmente seguivo questo stile di approccio, ma alla fine ho usato la soluzione che Vincent ha pubblicato sopra in quanto significava che non dovevo "modellare" anche il nome del file (cioè eliminare qualsiasi _, scrivere in maiuscolo! Ogni parola e quindi unire loro di nuovo).
mr_urf,

con sottodirectory:...'/app/models/**/*.rb'
artemave

Object.subclasses_of è obsoleto dopo v2.3.8.
David J.

11

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


7
Questo è carino poiché, in Rails 3, i tuoi modelli non vengono caricati automaticamente per impostazione predefinita, quindi molti dei metodi sopra riportati non restituiranno tutti i modelli possibili. La mia permutazione cattura anche modelli in plugin e sottodirectory:Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
wbharding,

2
@wbharding È molto carino, ma si risolve in errore quando tenta di rendere costanti i nomi dei test del mio modello rspec. ;-)
Ajedi32

@wbharding bella soluzione ma si rompe quando si hanno modelli spaziali
Marcus Mansur

10

ActiveRecord::Base.connection.tables


Anche un buon follow-up è <nome_tabella> .nome_colonna per elencare tutte le colonne nella tabella. Quindi per la tua tabella utente eseguiresti User.column_names
Mark Locklear

Questo ti porterà tutti i tavoli, non solo i modelli, poiché alcuni tavoli non hanno sempre modelli associati.
courtimas,

7

In una sola riga:

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

2
Questo non mostra tutti i modelli per me. Non so perché. È un paio di brevi, in effetti.
courtimas,

1
ha funzionato per me. 'solo un po' in ritardo per rispondere è tutto. dagli tempo.
boulder_ruby,

2
Probabilmente è necessario Rails.application.eager_load!prima dell'esecuzione in modalità di sviluppo.
denis.peplin,

7

Non posso ancora commentare, ma penso che la risposta sj26 dovrebbe essere la risposta migliore. Solo un suggerimento:

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

6

Con Rails 6 , Zetiwerk è diventato il caricatore di codice predefinito.

Per un caricamento desideroso, prova:

Zeitwerk::Loader.eager_load_all

Poi

ApplicationRecord.descendants

5

Sì, ci sono molti modi in cui puoi trovare tutti i nomi dei modelli ma quello che ho fatto nella mia gemma model_info è che ti darà tutti i modelli anche inclusi nelle gemme.

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

quindi stampalo semplicemente

@model_array

3

Questo funziona con 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

upvolt per quel Rails.application.eager_load! idea
equivalente8

3

Per evitare il precaricamento di tutti i binari, è possibile effettuare ciò:

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

require_dependency (f) è lo stesso che Rails.application.eager_load!utilizza. Questo dovrebbe evitare errori di file già richiesti.

Quindi è possibile utilizzare tutti i tipi di soluzioni per elencare i modelli AR, come ActiveRecord::Base.descendants


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

genera TypeError: nessuna conversione implicita di Symbol in String nella console.
Snowangel,

1

Ecco una soluzione che è stata esaminata con una complessa app Rails (quella che alimenta 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

Prende le parti migliori delle risposte in questo thread e le combina nella soluzione più semplice e completa. Gestisce i casi in cui i tuoi modelli si trovano in sottodirectory, usa set_table_name ecc.


1

Ho appena trovato questo, poiché ho bisogno di stampare tutti i modelli con i loro attributi (basato sul commento di @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

Questo ha funzionato per me. Un ringraziamento speciale a tutti i post sopra. Questo dovrebbe restituire una raccolta di tutti i tuoi modelli.

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

I Railsimplementa il metodo descendants, ma modelli non necessariamente sempre ereditato da ActiveRecord::Base, per esempio, la classe che comprende il modulo ActiveModel::Modelavrà lo stesso comportamento come modello, semplicemente non saranno collegati ad un tavolo.

A complemento di ciò che dicono i colleghi sopra, il minimo sforzo farebbe questo:

Patch di scimmia della classe Classdel Rubino:

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

e il metodo models, inclusi gli antenati, come questo:

Il metodo Module.constantsrestituisce (superficialmente) una raccolta di symbols, anziché costanti, quindi il metodo Array#selectpuò essere sostituito come questa patch di scimmia di 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

Patch di scimmia di 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

E, infine, il metodo dei modelli

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

Questo ti darà tutte le classi di modello che hai sul tuo progetto.


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

Ho provato così tante di queste risposte senza successo in Rails 4 (wow hanno cambiato una cosa o due per l'amor di Dio), ho deciso di aggiungere la mia. Quelli che hanno chiamato ActiveRecord :: Base.connection e hanno estratto i nomi delle tabelle hanno funzionato ma non hanno ottenuto il risultato che volevo perché ho nascosto alcuni modelli (in una cartella all'interno di app / models /) che non volevo Elimina:

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

L'ho messo in un inizializzatore e posso chiamarlo da qualsiasi luogo. Previene l'uso del mouse non necessario.


0

posso controllare questo

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

0

Supponendo che tutti i modelli siano in app / modelli e hai grep & awk sul tuo server (la maggior parte dei casi),

# 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")

È più veloce di Rails.application.eager_load!o ciclica attraverso ogni file con Dir.

MODIFICARE:

Lo svantaggio di questo metodo è che manca modelli che ereditano indirettamente da ActiveRecord (ad es FictionalBook < Book.). Il modo più sicuro è Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name), anche se è un po 'lento.


0

Sto solo dando questo esempio qui se qualcuno lo trova utile. La soluzione si basa su questa risposta https://stackoverflow.com/a/10712838/473040 .

Supponiamo che tu abbia una colonna public_uidche viene utilizzata come ID primario per il mondo esterno (puoi trovare i motivi per cui vorresti farlo qui )

Ora supponiamo che tu abbia introdotto questo campo su un mucchio di modelli esistenti e ora desideri rigenerare tutti i record che non sono stati ancora impostati. Puoi farlo in questo modo

# 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

ora puoi correre rake di:public_uids:generate

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.