ruby on rails - render_to_string - ¿Hay alguna forma de obtener una colección de todos los Modelos en su aplicación Rails?
routes in ruby on rails (26)
¿Hay alguna forma de obtener una colección de todos los Modelos en su aplicación Rails?
Básicamente, ¿puedo hacer lo siguiente de:
Models.each do |model|
puts model.class.name
end
Acabo de encontrar este, ya que necesito imprimir todos los modelos con sus atributos (basado 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}"}}
Aquí hay una solución que ha sido probada con una aplicación compleja de Rails (la que alimenta 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 los casos donde tus modelos están en subdirectorios, usa set_table_name, etc.
Asumiendo que todos los modelos están en la aplicación / modelos y que tienes grep & awk en tu 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 que Rails.application.eager_load!
o recorriendo cada archivo con Dir
.
EDITAR:
La desventaja de este método es que falla en los 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)
Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)
, aunque es un poco lento.
Busqué maneras 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
Creo que la solución de @ hnovick es genial si no tienes modelos sin tablas. Esta solución funcionaría también en modo de desarrollo
Mi enfoque es sutilmente diferente -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
se supone que clasifique para darle el nombre de la clase de una cadena correctamente . safe_constantize garantiza que puede convertirlo en una clase segura sin lanzar una excepción. Esto es necesario en caso de que tenga tablas de bases de datos que no sean modelos. compacto para eliminar cualquier nils en la enumeración.
En caso de que alguien se tropiece con este, tengo otra solución, no confiar en leer dir o extender la clase Class ...
ActiveRecord::Base.send :subclasses
Esto devolverá una matriz de clases. Entonces puedes hacer
ActiveRecord::Base.send(:subclasses).map(&:name)
En una línea: Dir[''app/models//*.rb''].map {|f| File.basename(f, ''.*'').camelize.constantize }
Dir[''app/models//*.rb''].map {|f| File.basename(f, ''.*'').camelize.constantize }
En una sola línea:
ActiveRecord::Base.subclasses.map(&:name)
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
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
Esto parece funcionar para mí:
Dir.glob(RAILS_ROOT + ''/app/models/*.rb'').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
Los rieles solo cargan 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 %>
He intentado muchas de estas respuestas sin éxito en Rails 4 (wow cambiaron una cosa o dos por el amor de Dios) Decidí agregar la mía. Los que llamaron a ActiveRecord :: Base.connection y extrajeron los nombres de la tabla funcionaron pero no obtuvieron el resultado que quería porque he ocultado algunos modelos (en una carpeta dentro de la aplicación / modelos /) que no quería borrar:
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. Impide el uso innecesario del mouse.
La respuesta completa para Rails 3, 4 y 5 es:
Si cache_classes
está desactivado (por defecto está desactivado en desarrollo, pero en producción):
Rails.application.eager_load!
Entonces:
ActiveRecord::Base.descendants
Esto asegura que todos los modelos en su aplicación, independientemente de dónde se encuentren, se carguen, y también se cargan las gemas que esté utilizando y que proporcionen modelos.
Esto también debería funcionar en las clases que heredan de ActiveRecord::Base
, como ApplicationRecord
en Rails 5, y devuelve solo ese subárbol de descendientes:
ApplicationRecord.descendants
Si desea obtener más información acerca de cómo se hace esto, consulte ActiveSupport::DescendantsTracker .
No puedo comentar todavía, pero creo que la respuesta sj26 debería ser la mejor respuesta. Solo una pista:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
Para Rails5, los modelos ahora son subclases de ApplicationRecord
por lo tanto, 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 se encuentra en el modo de desarrollo, necesitará cargar modelos antes de:
Rails.application.eager_load!
Para evitar precargar todos los Rails, 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!
usos. Esto debería evitar los errores de archivo ya requeridos.
Luego puede usar todo tipo de soluciones para enumerar modelos AR, como ActiveRecord::Base.descendants
Sí, hay muchas formas en que puede encontrar todos los nombres de modelo, pero lo que hice en mi gem es: model_info , le dará todos los modelos 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 imprime esto
@model_array
Si solo desea los nombres de clase:
ActiveRecord::Base.descendants.map {|f| puts f}
Simplemente ejecútelo en la consola de Rails, nada más. ¡Buena suerte!
EDITAR: @ sj26 es correcto, necesita ejecutar esto primero para poder llamar a los descendientes:
Rails.application.eager_load!
Solo estoy lanzando este ejemplo aquí si alguien lo encuentra útil. La solución se basa en esta respuesta https://.com/a/10712838/473040 .
Supongamos que tiene una columna public_uid
que se utiliza como ID principal para el mundo exterior (puede encontrar razones por las que le gustaría hacer eso here )
Ahora supongamos que ha introducido este campo en muchos modelos existentes y ahora desea regenerar todos los registros que aún no están configurados. 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 puede ejecutar rake di:public_uids:generate
The Rails
implementa los descendants
del método, pero los modelos no necesariamente heredan de ActiveRecord::Base
, por ejemplo, la clase que incluye el módulo ActiveModel::Model
tendrá el mismo comportamiento que un modelo, simplemente no se vinculará a un mesa.
Complementando lo que dicen los colegas de arriba, el más mínimo esfuerzo haría esto:
Monkey Patch de la clase Class
of the Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
y los models
métodos, incluidos los ancestros, como este:
El método Module.constants
devuelve (superficialmente) una colección de symbols
, en lugar de constantes, entonces, el método Array#select
puede ser sustituido como este parche mono del 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
Monkey parche 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
puede verificar esto
@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
ActiveRecord::Base.connection.tables
EDITAR: Mire los comentarios y otras respuestas. ¡Hay respuestas más inteligentes que ésta! O intenta mejorar este como wiki de la comunidad.
Los modelos no se registran en un objeto maestro, entonces no, Rails no tiene la lista de modelos.
Pero aún podrías mirar el contenido del directorio de modelos de tu aplicación ...
Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end
EDITAR: Otra idea (salvaje) sería usar la reflexión de Ruby para buscar todas las clases que amplíen ActiveRecord :: Base. No sé cómo puedes enumerar todas las clases, aunque ...
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 los 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 quieres manejar la clase derivada, necesitarás probar toda la cadena de la 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
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
regresará
["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
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
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