有没有办法可以在Rails应用程序中获得所有模型的集合?
基本上,我可以这样做: -
Models.each do |model|
puts model.class.name
end
答案 0 :(得分:360)
Rails 3,4和5的整个答案是:
如果cache_classes
已关闭(默认情况下,它在开发中已关闭,但在生产中已关闭):
Rails.application.eager_load!
然后:
ActiveRecord::Base.descendants
这可确保应用程序中的所有模型(无论它们在何处)都已加载,并且还会加载您正在使用的提供模型的任何宝石。
这也应该适用于继承自ActiveRecord::Base
的类,如Rails 5中的ApplicationRecord
,并且只返回后代的子树:
ApplicationRecord.descendants
如果您想了解更多有关 的信息,请查看ActiveSupport::DescendantsTracker。
答案 1 :(得分:112)
万一有人绊倒了这个,我有另一个解决方案,不依赖于读取或扩展Class类......
ActiveRecord::Base.send :subclasses
这将返回一个类数组。所以你可以这样做
ActiveRecord::Base.send(:subclasses).map(&:name)
答案 2 :(得分:95)
编辑:看看评论和其他答案。有比这更聪明的答案!或者尝试将此作为社区维基进行改进。
模型不会将自己注册到主对象,所以不,Rails没有模型列表。
但您仍然可以查看应用程序的models目录的内容......
Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end
编辑:另一个(狂野的)想法是使用Ruby反射来搜索扩展ActiveRecord :: Base的每个类。不知道如何列出所有课程......
编辑:为了好玩,我找到了列出所有课程的方法
Module.constants.select { |c| (eval c).is_a? Class }
编辑:最后成功列出所有模型而不查看目录
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
如果你也想处理派生类,那么你需要测试整个超类链。我是通过向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
答案 3 :(得分:64)
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
将返回
["Article", "MenuItem", "Post", "ZebraStripePerson"]
附加信息如果要在没有模型的情况下调用对象名称上的方法:string unknown method或variable errors使用此
model.classify.constantize.attribute_names
答案 4 :(得分:31)
我想方设法做到这一点并最终选择了这种方式:
in the controller:
@data_tables = ActiveRecord::Base.connection.tables
in the view:
<% @data_tables.each do |dt| %>
<br>
<%= dt %>
<% end %>
<br>
来源:http://portfo.li/rails/348561-how-can-one-list-all-database-tables-from-one-project
答案 5 :(得分:22)
如果您没有无桌面型号,我认为@ hnovick的解决方案很酷。此解决方案也适用于开发模式
虽然我的方法略有不同 -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
classify很适合从字符串正确中为您提供类的名称。 safe_constantize确保您可以安全地将其转换为类而不会抛出异常。如果您的数据库表不是模型,则需要这样做。紧凑,以便删除枚举中的任何nils。
答案 6 :(得分:21)
如果您只想要班级名称:
ActiveRecord::Base.descendants.map {|f| puts f}
只需在Rails控制台中运行它,仅此而已。祝你好运!
编辑:@ sj26是对的,你需要先运行它才能调用后代:
Rails.application.eager_load!
答案 7 :(得分:21)
对于ApplicationRecord
的 Rails5 模型are now subclasses,以便获取您应用中所有模型的列表:
ApplicationRecord.descendants.collect { |type| type.name }
或更短:
ApplicationRecord.descendants.collect(&:name)
如果您处于开发模式,则需要在以下情况下急切加载模型:
Rails.application.eager_load!
答案 8 :(得分:17)
这似乎对我有用:
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
Rails仅在使用时加载模型,因此Dir.glob行“需要”models目录中的所有文件。
一旦你有一个数组中的模型,你可以做你想的(例如在视图代码中):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
答案 9 :(得分:11)
在一行:Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
答案 10 :(得分:9)
ActiveRecord::Base.connection.tables
答案 11 :(得分:7)
只需一行:
ActiveRecord::Base.subclasses.map(&:name)
答案 12 :(得分:6)
我还不能发表评论,但我认为sj26 answer应该是最佳答案。只是一个提示:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
答案 13 :(得分:4)
是的,有很多方法可以找到所有模型名称,但我在我的宝石model_info中所做的是,它会为你提供所有甚至包含在宝石中的模型。
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
然后只需打印此
@model_array
答案 14 :(得分:3)
为避免预加载所有Rails,您可以这样做:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency(f)与Rails.application.eager_load!
使用的相同。这应该避免已经需要的文件错误。
然后您可以使用所有类型的解决方案来列出AR模型,例如ActiveRecord::Base.descendants
答案 15 :(得分:3)
这适用于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
答案 16 :(得分:2)
使用 Rails 6 ,Zetiwerk成为默认的代码加载器。
对于急切的加载,请尝试:
Zeitwerk::Loader.eager_load_all
然后
ApplicationRecord.descendants
答案 17 :(得分:2)
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
答案 18 :(得分:1)
这对我有用。特别感谢上面的所有帖子。这应该返回所有模型的集合。
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
答案 19 :(得分:1)
刚刚遇到这个,因为我需要打印所有带有属性的模型(基于@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}"}}
答案 20 :(得分:1)
Rails
实现方法descendants
,但模型不一定从ActiveRecord::Base
继承,例如,包含模块ActiveModel::Model
的类将具有相同的行为作为一个模型,只是没有链接到一个表。
所以补充上述同事所说的,最轻微的努力就是这样做:
Ruby的类Class
的Monkey Patch:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
和方法models
,包括祖先,如下:
方法Module.constants
返回(表面上)symbols
的集合,而不是常量,因此,方法Array#select
可以替换为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
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
最后,模型方法
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
答案 21 :(得分:1)
这是一个经过审核的复杂Rails应用程序(一个供电方块)的解决方案
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
它采用了该主题中答案的最佳部分,并将它们组合在最简单,最彻底的解决方案中。这处理模型位于子目录中的情况,使用set_table_name等。
答案 22 :(得分:0)
我已经在 Rails 4 中尝试了很多这些答案但是没有成功(哇他们为了上帝的缘故改变了一两件事)我决定添加自己的。调用ActiveRecord :: Base.connection并拉出表名的工作但是没有得到我想要的结果,因为我隐藏了一些我不想要的模型(在app / models /中的文件夹中)删除:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
我把它放在初始化器中,可以从任何地方调用它。防止不必要的鼠标使用。
答案 23 :(得分:0)
假设所有型号都在app / models中并且你有grep&amp; awk在您的服务器上(大多数情况),
# 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")
它比Rails.application.eager_load!
更快或使用Dir
循环遍历每个文件。
修改强>
这种方法的缺点是它错过了间接从ActiveRecord继承的模型(例如FictionalBook < Book
)。最可靠的方法是Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)
,即使它有点慢。
答案 24 :(得分:0)
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
答案 25 :(得分:0)
如果有人发现它有用,我只是把这个例子放在这里。解决方案基于此答案https://stackoverflow.com/a/10712838/473040。
假设您有一列public_uid
用作外部世界的主要ID(您可以找到为什么要这样做here)
现在假设您已经在一堆现有模型中引入了此字段,现在您想要重新生成尚未设置的所有记录。你可以这样做
# 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
您现在可以运行rake di:public_uids:generate
答案 26 :(得分:0)
可以查看
@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
答案 27 :(得分:0)
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
这将为您提供项目中所有的模型类。