Existe-t-il un moyen d'obtenir une collection de tous les modèles dans votre application Rails?
Fondamentalement, puis-je faire les goûts de: -
Models.each do |model|
puts model.class.name
end
EDIT: Regardez les commentaires et autres réponses. Il existe des réponses plus intelligentes que celle-ci! Ou essayez d'améliorer celui-ci en tant que wiki de communauté.
Les modèles ne s'enregistrent pas eux-mêmes dans un objet maître. Par conséquent, Rails ne dispose pas de la liste des modèles.
Mais vous pouvez toujours regarder dans le contenu du répertoire des modèles de votre application ...
Dir.foreach("#{Rails_ROOT}/app/models") do |model_path|
# ...
end
EDIT: Une autre idée (sauvage) consisterait à utiliser la réflexion Ruby pour rechercher toutes les classes qui étendent ActiveRecord :: Base. Je ne sais pas comment vous pouvez lister toutes les classes cependant ...
EDIT: Juste pour le plaisir, j'ai trouvé un moyen de lister toutes les classes
Module.constants.select { |c| (eval c).is_a? Class }
EDIT: a finalement réussi à lister tous les modèles sans regarder les annuaires
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
Si vous souhaitez également gérer la classe dérivée, vous devez tester toute la chaîne de la super-classe. Je l'ai fait en ajoutant une méthode à la 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
La réponse complète pour Rails 3, 4 et 5 est la suivante:
Si cache_classes
est désactivé (par défaut, il est désactivé en développement mais en production):
Rails.application.eager_load!
Ensuite:
ActiveRecord::Base.descendants
Cela garantit que tous les modèles de votre application, quel que soit leur emplacement, sont chargés, ainsi que tous les gemmes que vous utilisez et fournissant des modèles sont également chargés.
Cela devrait également fonctionner sur les classes qui héritent de ActiveRecord::Base
, comme ApplicationRecord
dans Rails 5, et renvoient uniquement ce sous-arbre de descendants:
ApplicationRecord.descendants
Si vous souhaitez en savoir plus sur comment, cliquez sur ActiveSupport :: DescendantsTracker .
Juste au cas où quelqu'un trébucherait sur celui-ci, j'ai une autre solution, ne pas compter sur la lecture directe ou l'extension de la classe de classe ...
ActiveRecord::Base.send :subclasses
Cela retournera un tableau de classes. Alors vous pouvez alors faire
ActiveRecord::Base.send(:subclasses).map(&:name)
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
reviendra
["Article", "MenuItem", "Post", "ZebraStripePerson"]
Informations complémentaires Si vous souhaitez appeler une méthode sur le nom de l'objet sans modèle: string unknown méthode ou les erreurs de variable utilisent this
model.classify.constantize.attribute_names
J'ai cherché des moyens de le faire et j'ai finalement choisi cette voie:
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-how-can-one-list-all-database-tables-from-one-project
Je pense que la solution de @ hnovick est cool si vous n’avez pas de modèles sans tableau. Cette solution fonctionnerait aussi en mode développement
Mon approche est légèrement différente cependant -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
classify est censé vous donner le nom de la classe à partir d'une chaîne correctement . safe_constantize vous permet de transformer cette classe en classe en toute sécurité, sans exception. Ceci est nécessaire si vous avez des tables de base de données qui ne sont pas des modèles. compacte de sorte que les nils dans l'énumération soient supprimés.
Si vous voulez juste les noms de classe:
ActiveRecord::Base.descendants.map {|f| puts f}
Il suffit de l’exécuter dans la console Rails, rien de plus. Bonne chance!
EDIT: @ sj26 a raison, vous devez l'exécuter avant de pouvoir appeler des descendants:
Rails.application.eager_load!
Pour Rails5 modèles sont maintenant des sous-classes de ApplicationRecord
afin d'obtenir la liste de tous les modèles de votre application que vous faites:
ApplicationRecord.descendants.collect { |type| type.name }
Ou plus court:
ApplicationRecord.descendants.collect(&:name)
Si vous êtes en mode dev, vous devez charger avec impatience les modèles avant de:
Rails.application.eager_load!
Cela semble fonctionner pour moi:
Dir.glob(Rails_ROOT + '/app/models/*.rb').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
Rails ne charge les modèles que lorsqu'ils sont utilisés. La ligne Dir.glob "requiert" tous les fichiers du répertoire des modèles.
Une fois que vous avez les modèles dans un tableau, vous pouvez faire ce que vous pensiez (par exemple, dans le code d'affichage):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
Sur une ligne: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
ActiveRecord::Base.connection.tables
En une seule ligne:
ActiveRecord::Base.subclasses.map(&:name)
Je ne peux pas encore commenter, mais je pense que sj26 answer devrait être la première des réponses. Juste un indice:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
Oui, il existe de nombreuses façons de trouver tous les noms de modèles, mais ce que j’ai fait dans ma gemme model_info est que vous obtiendrez tous les modèles même inclus dans les gemmes.
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
puis imprimez simplement ceci
@model_array
Cela fonctionne pour 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
Pour éviter de pré-charger tous les Rails, procédez comme suit:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency (f) est le même que Rails.application.eager_load!
utilise. Cela devrait éviter les erreurs de fichier déjà requises.
Ensuite, vous pouvez utiliser toutes sortes de solutions pour lister les modèles AR, comme ActiveRecord::Base.descendants
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
Cela a fonctionné pour moi. Un merci spécial à tous les messages ci-dessus. Cela devrait renvoyer une collection de tous vos modèles.
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
Rails
implémente la méthode descendants
, mais les modèles n'héritent pas nécessairement de ActiveRecord::Base
, par exemple, la classe qui inclut le module ActiveModel::Model
aura le même comportement qu'un modèle, mais ne sera pas liée à une table.
Donc, en complément de ce que disent les collègues ci-dessus, le moindre effort ferait ceci:
Singe Patch de classe Class
du Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
et la méthode models
, y compris les ancêtres, comme ceci:
La méthode Module.constants
renvoie (superficiellement) une collection de symbols
, au lieu de constantes. La méthode Array#select
peut donc être remplacée de la même manière que ce correctif pour le singe de la 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 de singe de 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
Et enfin, la méthode des modèles
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
Voici une solution qui a été validée avec une application Rails complexe (celle qui alimente 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
Il prend les meilleures parties des réponses dans ce fil et les combine dans la solution la plus simple et la plus complète. Cette poignée des cas où vos modèles sont dans des sous-répertoires, utilisez set_table_name etc.
Je viens de trouver celui-ci, car je dois imprimer tous les modèles avec leurs attributs (construits sur le commentaire de @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}"}}
En supposant que tous les modèles sont dans app/models et que vous avez grep & awk sur votre serveur (dans la majorité des cas),
# 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")
Il est plus rapide que Rails.application.eager_load!
ou parcourt chaque fichier avec Dir
.
MODIFIER:
Le désavantage de cette méthode est qu’il manque des modèles qui héritent indirectement d’ActiveRecord (par exemple, FictionalBook < Book
). Le moyen le plus sûr est Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)
, même si c'est un peu lent.
peut vérifier cela
@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
Cela vous donnera toutes les classes de modèle que vous avez sur votre projet.
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
J'ai essayé tant de réponses sans succès dans Rails 4 (wow, ils ont changé une chose ou deux pour l'amour de dieu) J'ai décidé d'ajouter la mienne. Ceux qui ont appelé ActiveRecord :: Base.connection et extrait les noms de table ont fonctionné, mais n'ont pas obtenu le résultat escompté, car j'ai caché certains modèles (dans un dossier situé dans app/models /) que je ne voulais pas. effacer:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
Je mets cela dans un initialiseur et peux l'appeler de n'importe où. Empêche l'utilisation inutile de la souris.
Je lance juste cet exemple ici si quelqu'un le trouve utile. La solution est basée sur cette réponse https://stackoverflow.com/a/10712838/473040 .
Supposons que vous ayez une colonne public_uid
utilisée comme identifiant principal du monde extérieur (vous pouvez trouver les raisons pour lesquelles vous voudriez le faire ici )
Supposons maintenant que vous avez introduit ce champ dans un ensemble de modèles existants et que vous souhaitiez maintenant régénérer tous les enregistrements non encore définis. Tu peux faire ça comme ça
# 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
vous pouvez maintenant exécuter rake di:public_uids:generate