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
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
Risposte:
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
RAILS_ROOT
non è più disponibile in Rails 3. Usa inveceDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
adesso, quindi se desideri caricare tutti i modelli, puoi iterarli facilmente — vedi la mia risposta di seguito.
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 ApplicationRecord
in 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 .
:environment
dal fatto eager_load!
che funzioni.
Rails.application.eager_load!
, puoi semplicemente caricare i modelli:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
directory. 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.
Rails.application.paths["app/models"].eager_load!
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)
ActiveRecord::Base.subclasses
ma devi usare send
? Inoltre, sembra che devi "toccare" il modello prima che venga visualizzato, ad esempio c = Category.new
e verrà visualizzato. Altrimenti no.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
elencarli.
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
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.
.capitalize.singularize.camelize
può essere sostituito a .classify
.
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
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.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Per Rails5 i modelli sono ora sottoclassi di ApplicationRecord
così 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!
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.
safe_constantize
.
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!
map
con puts
? Non capisco il punto dovrebbe essereActiveRecord::Base.descendants.map(&:model_name)
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 %>
...'/app/models/**/*.rb'
Su una riga: 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 una sola riga:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
prima dell'esecuzione in modalità di sviluppo.
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
Con Rails 6 , Zetiwerk è diventato il caricatore di codice predefinito.
Per un caricamento desideroso, prova:
Zeitwerk::Loader.eager_load_all
Poi
ApplicationRecord.descendants
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
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
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
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
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.
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}"}}
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
I Rails
implementa il metodo descendants
, ma modelli non necessariamente sempre ereditato da ActiveRecord::Base
, per esempio, la classe che comprende il modulo ActiveModel::Model
avrà 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 Class
del 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.constants
restituisce (superficialmente) una raccolta di symbols
, anziché costanti, quindi il metodo Array#select
può 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
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
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.
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.
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_uid
che 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