¿Hay alguna manera de que puedas obtener una colección de todos los Modelos en tu aplicación Rails?
Básicamente, ¿puedo hacer los gustos de: -
Models.each do |model|
puts model.class.name
end
¿Hay alguna manera de que puedas obtener una colección de todos los Modelos en tu aplicación Rails?
Básicamente, ¿puedo hacer los gustos de: -
Models.each do |model|
puts model.class.name
end
Respuestas:
EDITAR: mira los comentarios y otras respuestas. ¡Hay respuestas más inteligentes que esta! O intente mejorar este como wiki comunitario.
Los modelos no se registran en un objeto maestro, por lo que no, Rails no tiene la lista de modelos.
Pero aún puede buscar en el contenido del directorio de modelos de su aplicación ...
Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end
EDITAR: Otra idea (salvaje) sería usar la reflexión Ruby para buscar todas las clases que extiendan ActiveRecord :: Base. Sin embargo, no sé cómo puedes enumerar todas las clases ...
EDITAR: solo por diversión, encontré una manera de enumerar todas las clases
Module.constants.select { |c| (eval c).is_a? Class }
EDITAR: Finalmente logró enumerar todos los modelos sin mirar directorios
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 también desea manejar la clase derivada, deberá probar toda la cadena de superclase. Lo hice agregando un método a la clase 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
ya no está disponible en Rails 3. En su lugar, useDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
ahora, por lo que si desea cargar todos los modelos, puede iterarlos fácilmente; vea mi respuesta a continuación.
La respuesta completa para los rieles 3, 4 y 5 es:
Si cache_classes
está desactivado (por defecto está desactivado en desarrollo, pero activado en producción):
Rails.application.eager_load!
Luego:
ActiveRecord::Base.descendants
Esto asegura que todos los modelos en su aplicación, independientemente de dónde se encuentren, se carguen y las gemas que está utilizando que proporcionan modelos también se cargan.
Esto también debería funcionar en clases que heredan ActiveRecord::Base
, como ApplicationRecord
en Rails 5, y devuelven solo ese subárbol de descendientes:
ApplicationRecord.descendants
Si desea saber más sobre cómo se hace esto, consulte ActiveSupport :: DescendantsTracker .
:environment
para eager_load!
que funcione.
Rails.application.eager_load!
, simplemente puede cargar los modelos:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
directorios. La ansiosa carga de toda la aplicación es una respuesta más completa y se asegurará de que no quede absolutamente ningún lugar para definir los modelos.
Rails.application.paths["app/models"].eager_load!
En caso de que alguien tropiece con este, tengo otra solución, no confiar en la lectura de directorios o extender la clase Class ...
ActiveRecord::Base.send :subclasses
Esto devolverá una variedad de clases. Entonces puedes hacer
ActiveRecord::Base.send(:subclasses).map(&:name)
ActiveRecord::Base.subclasses
pero tienes que usar send
? Además, parece que tiene que "tocar" el modelo antes de que aparezca, por ejemplo, c = Category.new
y aparecerá. De lo contrario, no lo hará.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
enumerarlos.
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
volverá
["Article", "MenuItem", "Post", "ZebraStripePerson"]
Información adicional Si desea llamar a un método en el nombre del objeto sin modelo: método desconocido de cadena o errores variables, use esto
model.classify.constantize.attribute_names
ActiveRecord::Base.send :subclasses
: buscar los nombres de las tablas es una buena idea. Generar automáticamente los nombres de los modelos puede ser problemático como se menciona anteriormente.
.capitalize.singularize.camelize
puede ser reemplazado a .classify
.
Busqué formas de hacer esto y terminé eligiendo de esta manera:
in the controller:
@data_tables = ActiveRecord::Base.connection.tables
in the view:
<% @data_tables.each do |dt| %>
<br>
<%= dt %>
<% end %>
<br>
fuente: 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}
algunos de los modelos pueden no estar activados, por lo tanto, debe rescatarlo.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Para Rails5, los modelos ahora son subclases de ApplicationRecord
modo que para obtener una lista de todos los modelos en su aplicación, haga lo siguiente:
ApplicationRecord.descendants.collect { |type| type.name }
O más corto:
ApplicationRecord.descendants.collect(&:name)
Si está en modo de desarrollo, necesitará cargar modelos antes de:
Rails.application.eager_load!
Creo que la solución de @ hnovick es genial si no tienes modelos sin mesa. Esta solución también funcionaría en modo de desarrollo
Sin embargo, mi enfoque es sutilmente diferente:
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
Se supone que classify te da el nombre de la clase de una cadena correctamente . safe_constantize asegura que puede convertirlo en una clase de forma segura sin lanzar una excepción. Esto es necesario en caso de que tenga tablas de base de datos que no sean modelos. compacto para que se eliminen los nulos en la enumeración.
safe_constantize
.
Si solo quieres los nombres de clase:
ActiveRecord::Base.descendants.map {|f| puts f}
Simplemente ejecútalo en la consola de Rails, nada más. ¡Buena suerte!
EDITAR: @ sj26 es correcto, primero debe ejecutar esto antes de poder llamar a los descendientes:
Rails.application.eager_load!
map
con puts
? No entiendo el punto debería serActiveRecord::Base.descendants.map(&:model_name)
Esto parece funcionar para mí:
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
Rails solo carga modelos cuando se usan, por lo que la línea Dir.glob "requiere" todos los archivos en el directorio de modelos.
Una vez que tenga los modelos en una matriz, puede hacer lo que estaba pensando (por ejemplo, en el código de vista):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
...'/app/models/**/*.rb'
En una línea: 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 solo una línea:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
antes de la ejecución en modo de desarrollo.
Todavía no puedo comentar, pero creo que la respuesta sj26 debería ser la respuesta principal. Solo una pista:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
Sí, hay muchas formas de encontrar todos los nombres de modelos, pero lo que hice en mi gema model_info es que te dará todos los modelos incluso incluidos en las 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
entonces simplemente imprima esto
@model_array
Esto funciona para 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 la carga previa de todos los rieles, puede hacer esto:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency (f) es lo mismo que Rails.application.eager_load!
usa. Esto debería evitar errores de archivo ya requeridos.
Luego puede usar todo tipo de soluciones para enumerar modelos AR, como ActiveRecord::Base.descendants
Aquí hay una solución que ha sido examinada con una aplicación Rails compleja (la que impulsa 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
Toma las mejores partes de las respuestas en este hilo y las combina en la solución más simple y completa. Este maneja casos donde sus modelos están en subdirectorios, use set_table_name, etc.
Acabo de encontrar este, ya que necesito imprimir todos los modelos con sus atributos (basados en el comentario 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}"}}
Esto funcionó para mí. Un agradecimiento especial a todas las publicaciones anteriores. Esto debería devolver una colección de todos sus 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
Los Rails
implementa el método descendants
, pero los modelos no necesariamente siempre hereda de ActiveRecord::Base
, por ejemplo, la clase que incluye el móduloActiveModel::Model
tendrá el mismo comportamiento que un modelo, simplemente no estarán vinculados a una mesa.
Entonces, complementando lo que dicen los colegas anteriores, el más mínimo esfuerzo haría esto:
Monkey Patch de la clase Class
de Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
y el método models
, incluidos los ancestros, como este:
El método Module.constants
devuelve (superficialmente) una colección de symbols
, en lugar de constantes, por lo que el método Array#select
puede sustituirse como este parche de mono 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
Parche de mono 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
Y, finalmente, el método de los 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
He intentado muchas de estas respuestas sin éxito en Rails 4 (wow, cambiaron una o dos cosas por el amor de Dios) decidí agregar la mía. Los que llamaron a ActiveRecord :: Base.connection y obtuvieron los nombres de las tablas funcionaron pero no obtuvieron el resultado que quería porque escondí algunos modelos (en una carpeta dentro de app / models /) que no quería Eliminar:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
Lo puse en un inicializador y puedo llamarlo desde cualquier lugar. Previene el uso innecesario del mouse.
Suponiendo que todos los modelos están en la aplicación / modelos y que tiene grep & awk en su servidor (la mayoría de los 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")
Es más rápido Rails.application.eager_load!
o recorre cada archivo con Dir
.
EDITAR:
La desventaja de este método es que pierde modelos que heredan indirectamente de ActiveRecord (por ejemplo FictionalBook < Book
). La forma más segura es Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)
, aunque sea un poco lenta.
Solo estoy lanzando este ejemplo aquí si alguien lo encuentra útil. La solución se basa en esta respuesta https://stackoverflow.com/a/10712838/473040 .
Digamos que tiene una columna public_uid
que se utiliza como ID principal para el mundo exterior (puede encontrar las razones por las que desea hacer eso aquí )
Ahora supongamos que ha introducido este campo en un montón de modelos existentes y ahora desea regenerar todos los registros que aún no se han establecido. Puedes hacer eso así
# 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
ahora puedes correr rake di:public_uids:generate