web-dev-qa-db-fra.com

Existe-t-il un moyen d'obtenir une collection de tous les modèles de votre application Rails?

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

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

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 .

353
sj26

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

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

31
jaime

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.

22
Aditya Sanghi

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!
21

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

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

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

11
vjt

ActiveRecord::Base.connection.tables

8
Mark Locklear

En une seule ligne:

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

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

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

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

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

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

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

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

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.

1
Pascal-Louis Perez

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

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.

0
konyak

peut vérifier cela 

@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

Cela vous donnera toutes les classes de modèle que vous avez sur votre projet.

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

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.

0
boulder_ruby

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

0
equivalent8