Existe uma maneira de obter uma coleção de todos os modelos no seu aplicativo Rails?
Basicamente, posso fazer o seguinte: -
Models.each do |model|
puts model.class.name
end
Existe uma maneira de obter uma coleção de todos os modelos no seu aplicativo Rails?
Basicamente, posso fazer o seguinte: -
Models.each do |model|
puts model.class.name
end
Respostas:
Edição: Veja os comentários e outras respostas. Existem respostas mais inteligentes do que esta! Ou tente melhorar este como wiki da comunidade.
Os modelos não se registram em um objeto mestre; portanto, o Rails não possui a lista de modelos.
Mas você ainda pode procurar no conteúdo do diretório de modelos do seu aplicativo ...
Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end
Edição: Outra idéia (selvagem) seria usar a reflexão Ruby para procurar todas as classes que estendem ActiveRecord :: Base. Não sei como é possível listar todas as classes ...
EDIT: Apenas por diversão, encontrei uma maneira de listar todas as aulas
Module.constants.select { |c| (eval c).is_a? Class }
EDIT: finalmente conseguiu listar todos os modelos sem consultar os diretórios
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 você quiser lidar com a classe derivada também, precisará testar toda a cadeia da superclasse. Eu fiz isso adicionando um método à 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ão está mais disponível no Rails 3. Em vez disso, useDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
agora, portanto, se você carregar todos os modelos com entusiasmo, poderá iterá-los facilmente - veja minha resposta abaixo.
A resposta completa para o Rails 3, 4 e 5 é:
Se cache_classes
estiver desativado (por padrão, está desativado no desenvolvimento, mas ativado na produção):
Rails.application.eager_load!
Então:
ActiveRecord::Base.descendants
Isso garante que todos os modelos em seu aplicativo, independentemente de onde eles estejam, estejam carregados e quaisquer gemas que você esteja usando que forneçam modelos também sejam carregadas.
Isso também deve funcionar em classes que herdam de ActiveRecord::Base
, como ApplicationRecord
no Rails 5, e retornam apenas a subárvore de descendentes:
ApplicationRecord.descendants
Se você quiser saber mais sobre como isso é feito, consulte ActiveSupport :: DescendantsTracker .
:environment
para que o eager_load!
trabalho funcione.
Rails.application.eager_load!
, você pode apenas carregar os modelos:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
diretórios. O carregamento ansioso de todo o aplicativo é uma resposta mais completa e garantirá que não haja absolutamente nenhum lugar para os modelos serem definidos.
Rails.application.paths["app/models"].eager_load!
Apenas para o caso de alguém tropeçar nessa, eu tenho outra solução, não confiando na leitura de dir ou estendendo a classe Class ...
ActiveRecord::Base.send :subclasses
Isso retornará uma matriz de classes. Então você pode fazer
ActiveRecord::Base.send(:subclasses).map(&:name)
ActiveRecord::Base.subclasses
mas tem que usar send
? Além disso, parece que você precisa "tocar" o modelo antes que ele apareça, por exemplo, c = Category.new
e ele apareça. Caso contrário, não vai.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
aparecer , você ainda precisará "tocar" os modelos antes para listá-los.
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
retornará
["Article", "MenuItem", "Post", "ZebraStripePerson"]
Informações adicionais Se você desejar chamar um método no nome do objeto sem modelo: método desconhecido da cadeia ou erros de variáveis, use este
model.classify.constantize.attribute_names
ActiveRecord::Base.send :subclasses
procurar os nomes das tabelas é uma boa idéia. A geração automática dos nomes dos modelos pode ser problemática, como mencionado anteriormente.
.capitalize.singularize.camelize
pode ser substituído por .classify
.
Procurei maneiras de fazer isso e acabei escolhendo desta maneira:
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}
Alguns dos modelos podem não estar ativados; portanto, é necessário resgatá-lo.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Para os modelos Rails5 , agora são subclasses de ApplicationRecord
, para obter a lista de todos os modelos em seu aplicativo, você faz:
ApplicationRecord.descendants.collect { |type| type.name }
Ou mais curto:
ApplicationRecord.descendants.collect(&:name)
Se você estiver no modo dev, precisará de modelos de carregamento ansiosos antes:
Rails.application.eager_load!
Acho que a solução da @ hnovick é legal se você não tem modelos sem mesa. Essa solução funcionaria também no modo de desenvolvimento
Minha abordagem é sutilmente diferente -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
classify deve fornecer o nome da classe de uma string corretamente . safe_constantize garante que você possa transformá-lo em uma classe com segurança, sem gerar uma exceção. Isso é necessário caso você tenha tabelas de banco de dados que não são modelos. compacto para que quaisquer nulos na enumeração sejam removidos.
safe_constantize
.
Se você deseja apenas os nomes das classes:
ActiveRecord::Base.descendants.map {|f| puts f}
Basta executá-lo no console do Rails, nada mais. Boa sorte!
EDIT: @ sj26 está certo, você precisa executá-lo primeiro antes de poder chamar os descendentes:
Rails.application.eager_load!
map
com puts
? Eu não entendo o ponto deve serActiveRecord::Base.descendants.map(&:model_name)
Isso parece funcionar para mim:
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
O Rails só carrega modelos quando eles são usados, portanto a linha Dir.glob "requer" todos os arquivos no diretório models.
Depois de ter os modelos em uma matriz, você pode fazer o que estava pensando (por exemplo, no código de exibição):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
...'/app/models/**/*.rb'
Em uma linha: 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
Em apenas uma linha:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
antes da execução no modo de desenvolvimento.
Ainda não posso comentar, mas acho que a resposta sj26 deve ser a resposta principal. Apenas uma dica:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
Sim, existem muitas maneiras de encontrar todos os nomes de modelos, mas o que eu fiz na minha gema model_info é que ele fornecerá todos os modelos incluídos nas gemas.
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
então simplesmente imprima isso
@model_array
Isso funciona para o 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
Para evitar pré-carregar todos os Rails, você pode fazer o seguinte:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency (f) é o mesmo que Rails.application.eager_load!
usa. Isso deve evitar erros de arquivo já necessários.
Em seguida, você pode usar todo tipo de solução para listar modelos de AR, como ActiveRecord::Base.descendants
Aqui está uma solução que foi examinada com um aplicativo Rails complexo (aquele que alimenta o 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
Ele pega as melhores partes das respostas neste tópico e as combina na solução mais simples e completa. Este identificador de casos em que seus modelos estão em subdiretórios, use set_table_name etc.
Acabei de encontrar este, pois preciso imprimir todos os modelos com seus atributos (baseados no comentário 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}"}}
Isso funcionou para mim. Agradecimentos especiais a todas as postagens acima. Isso deve retornar uma coleção de todos os seus modelos.
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
Ele Rails
implementa o método descendants
, mas os modelos nem sempre herdam ActiveRecord::Base
, por exemplo, a classe que inclui o módulo ActiveModel::Model
terá o mesmo comportamento que um modelo, apenas não será vinculado a uma tabela.
Então, complementando o que dizem os colegas acima, o menor esforço faria o seguinte:
Patch de Macacos da classe Class
Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
e o método models
, incluindo ancestrais, como este:
O método Module.constants
retorna (superficialmente) uma coleção de symbols
, em vez de constantes, portanto, o método Array#select
pode ser substituído como este patch de macaco do 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
Remendo de macaco 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, finalmente, o método dos modelos
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
Eu tentei muitas dessas respostas sem sucesso no Rails 4 (uau, elas mudaram uma coisa ou duas por amor de Deus) e decidi adicionar as minhas. Aqueles que chamaram ActiveRecord :: Base.connection e puxaram os nomes das tabelas funcionaram, mas não obtiveram o resultado desejado, porque eu ocultei alguns modelos (em uma pasta dentro de app / models /) que eu não queria excluir:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
Coloquei isso em um inicializador e posso chamá-lo de qualquer lugar. Impede o uso desnecessário do mouse.
Supondo que todos os modelos estejam em app / models e você tenha grep & awk no seu servidor (na maioria dos casos),
# 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")
É mais rápido do que Rails.application.eager_load!
ou repetindo cada arquivo com Dir
.
EDITAR:
A desvantagem desse método é que ele perde modelos herdados indiretamente do ActiveRecord (por exemplo FictionalBook < Book
). O caminho mais certo é que Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)
, embora seja meio lento.
Estou apenas jogando este exemplo aqui, se alguém achar útil. A solução é baseada nesta resposta https://stackoverflow.com/a/10712838/473040 .
Digamos que você tenha uma coluna public_uid
que é usada como um ID primário para o mundo exterior (você pode encontrar perguntas sobre por que você faria isso aqui )
Agora, digamos que você introduziu esse campo em vários modelos existentes e agora deseja gerar novamente todos os registros que ainda não foram definidos. Você pode fazer isso assim
# 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
agora você pode correr rake di:public_uids:generate