Existe-t-il un moyen d'obtenir une collection de tous les modèles de votre application Rails?
Fondamentalement, puis-je faire des choses comme: -
Models.each do |model|
puts model.class.name
end
Existe-t-il un moyen d'obtenir une collection de tous les modèles de votre application Rails?
Fondamentalement, puis-je faire des choses comme: -
Models.each do |model|
puts model.class.name
end
Réponses:
EDIT: Regardez les commentaires et autres réponses. Il y a des réponses plus intelligentes que celle-ci! Ou essayez d'améliorer celui-ci en tant que wiki communautaire.
Les modèles ne s'enregistrent pas eux-mêmes dans un objet maître, donc non, Rails n'a pas 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) serait d'utiliser la réflexion Ruby pour rechercher toutes les classes qui étendent ActiveRecord :: Base. Je ne sais pas comment lister toutes les classes ...
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: Enfin réussi à lister tous les modèles sans regarder les répertoires
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 devrez tester l'ensemble de la chaîne de superclasse. 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
RAILS_ROOT
n'est plus disponible dans Rails 3. Au lieu de cela, utilisezDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
maintenant, donc si vous avez hâte de charger tous les modèles, vous pouvez les itérer facilement - voir ma réponse ci-dessous.
La réponse complète pour les rails 3, 4 et 5 est:
Si cache_classes
est désactivé (par défaut, il est désactivé en développement, mais activé 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 et toutes les gemmes que vous utilisez qui fournissent des modèles sont également chargées.
Cela devrait également fonctionner sur les classes qui héritent de ActiveRecord::Base
, comme ApplicationRecord
dans Rails 5, et renvoyer uniquement ce sous-arbre de descendants:
ApplicationRecord.descendants
Si vous souhaitez en savoir plus sur la façon dont cela est fait, consultez ActiveSupport :: DescendantsTracker .
:environment
du eager_load!
fonctionnement.
Rails.application.eager_load!
, vous pouvez simplement charger les modèles:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
répertoires. Désireux de charger toute l'application est une réponse plus complète et vous assurera qu'il n'y a absolument plus de place pour les modèles à définir.
Rails.application.paths["app/models"].eager_load!
Juste au cas où quelqu'un trébucherait sur celui-ci, j'ai une autre solution, ne pas compter sur la lecture de dir ou étendre la classe Class ...
ActiveRecord::Base.send :subclasses
Cela renverra un tableau de classes. Vous pouvez donc faire
ActiveRecord::Base.send(:subclasses).map(&:name)
ActiveRecord::Base.subclasses
mais devez utiliser send
? De plus, il semble que vous devez "toucher" le modèle avant qu'il n'apparaisse, par exemple c = Category.new
et qu'il apparaisse. Sinon, ce ne sera pas le cas.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
les énumérer.
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
reviendra
["Article", "MenuItem", "Post", "ZebraStripePerson"]
Informations supplémentaires Si vous souhaitez appeler une méthode sur le nom de l'objet sans modèle: chaîne méthode inconnue ou erreurs de variable utilisez ceci
model.classify.constantize.attribute_names
ActiveRecord::Base.send :subclasses
- rechercher les noms de table est une bonne idée. La génération automatique des noms de modèle peut être problématique, comme indiqué précédemment.
.capitalize.singularize.camelize
peut être remplacé par .classify
.
J'ai cherché des moyens de le faire et j'ai fini par choisir de cette façon:
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
ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}
Certains modèles peuvent ne pas être activés, vous devez donc le sauver.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Pour les modèles Rails5 sont maintenant des sous - classes de ApplicationRecord
afin d'obtenir la liste de tous les modèles dans votre application, vous faites:
ApplicationRecord.descendants.collect { |type| type.name }
Ou plus court:
ApplicationRecord.descendants.collect(&:name)
Si vous êtes en mode dev, vous devrez charger les modèles avant:
Rails.application.eager_load!
Je pense que la solution de @ hnovick est cool si vous n'avez pas de modèles sans table. Cette solution fonctionnerait également en mode développement
Mon approche est cependant subtilement différente -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
classify est bien censé vous donner correctement le nom de la classe à partir d'une chaîne . safe_constantize garantit que vous pouvez le transformer en classe en toute sécurité sans lever d'exception. Cela est nécessaire si vous avez des tables de base de données qui ne sont pas des modèles. compact pour que tous les nils de l'énumération soient supprimés.
safe_constantize
.
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 d'abord l'exécuter avant de pouvoir appeler des descendants:
Rails.application.eager_load!
map
avec puts
? Je ne comprends pas le point devrait êtreActiveRecord::Base.descendants.map(&:model_name)
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, donc la ligne Dir.glob "nécessite" tous les fichiers du répertoire models.
Une fois que vous avez les modèles dans un tableau, vous pouvez faire ce que vous pensiez (par exemple dans le code de vue):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
...'/app/models/**/*.rb'
Sur une ligne: 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
En une seule ligne:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
avant l'exécution en mode développement.
Je ne peux pas encore commenter, mais je pense que la réponse sj26 devrait être la meilleure réponse. 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 gem model_info est, cela vous donnera 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, vous pouvez procéder comme suit:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency (f) est le même que celui Rails.application.eager_load!
utilisé. Cela devrait éviter les erreurs de fichier déjà requises.
Ensuite, vous pouvez utiliser toutes sortes de solutions pour répertorier les modèles AR, comme ActiveRecord::Base.descendants
Voici une solution qui a été approuvé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 approfondie. Cela gère les cas où vos modèles sont dans des sous-répertoires, utilisez set_table_name etc.
Je viens de tomber sur celui-ci, car j'ai besoin d'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}"}}
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
L' 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.
Complétant ainsi ce que disent les collègues ci-dessus, le moindre effort ferait cela:
Patch de singe de la classe Class
du Rubis:
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, donc, la méthode Array#select
peut être substituée comme ce patch de singe de 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
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é tellement de ces 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 tiré les noms de table ont fonctionné mais n'ont pas obtenu le résultat que je voulais parce que j'ai caché certains modèles (dans un dossier à l'intérieur de l'application / models /) que je ne voulais pas supprimer:
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 je peux l'appeler de n'importe où. Empêche l'utilisation inutile de la souris.
En supposant que tous les modèles sont dans l'application / modèles et que vous avez grep & awk sur votre serveur (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")
C'est plus rapide que Rails.application.eager_load!
ou en boucle dans chaque fichier avec Dir
.
ÉDITER:
L'inconvénient de cette méthode est qu'elle 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.
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 ID principal pour le monde extérieur (vous pouvez trouver les raisons pour lesquelles vous voudriez le faire ici )
Supposons maintenant que vous ayez introduit ce champ sur un tas de modèles existants et que vous vouliez maintenant régénérer tous les enregistrements qui ne sont pas encore définis. Vous pouvez 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 courir rake di:public_uids:generate