有没有一种方法可以收集 Rails 应用程序中的所有模型?
基本上,我可以这样做: -
Models.each do |model|
puts model.class.name
end
有没有一种方法可以收集 Rails 应用程序中的所有模型?
基本上,我可以这样做: -
Models.each do |model|
puts model.class.name
end
Rails 3、4 和 5 的完整答案是:
如果cache_classes
关闭(默认情况下它在开发中关闭,但在生产中打开):
Rails.application.eager_load!
然后:
ActiveRecord::Base.descendants
这可以确保您的应用程序中的所有模型(无论它们在哪里)都被加载,并且您正在使用的提供模型的任何 gem 也被加载。
这也应该适用于继承自的类ActiveRecord::Base
,例如ApplicationRecord
在 Rails 5 中,并且只返回后代的子树:
ApplicationRecord.descendants
如果您想了解有关如何完成此操作的更多信息,请查看ActiveSupport::DescendantsTracker。
以防万一有人偶然发现这个,我有另一种解决方案,不依赖于 dir 阅读或扩展 Class 类......
ActiveRecord::Base.send :subclasses
这将返回一个类数组。所以你可以这样做
ActiveRecord::Base.send(:subclasses).map(&:name)
编辑:查看评论和其他答案。有比这个更聪明的答案!或者尝试将其改进为社区 wiki。
模型不会将自己注册到主对象,所以不,Rails 没有模型列表。
但是您仍然可以查看应用程序的模型目录的内容...
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
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
将返回
["Article", "MenuItem", "Post", "ZebraStripePerson"]
附加信息如果您想在没有模型的对象名称上调用方法:字符串未知方法或变量错误,请使用此
model.classify.constantize.attribute_names
对于Rails5模型现在是ApplicationRecord
so 的子类,要获取应用程序中所有模型的列表,您可以:
ApplicationRecord.descendants.collect { |type| type.name }
或更短:
ApplicationRecord.descendants.collect(&:name)
如果您处于开发模式,则需要先预先加载模型:
Rails.application.eager_load!
我寻找方法来做到这一点,最终选择了这种方式:
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
如果您没有无表模型,我认为@hnovick 的解决方案是一个很酷的解决方案。该解决方案也可以在开发模式下工作
不过,我的方法略有不同-
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
分类应该可以正确地从字符串中为您提供类的名称。safe_constantize 确保您可以安全地将其转换为类而不会引发异常。如果您有不是模型的数据库表,则需要这样做。紧凑,以便删除枚举中的任何 nil。
如果你只想要类名:
ActiveRecord::Base.descendants.map {|f| puts f}
只需在 Rails 控制台中运行它,仅此而已。祝你好运!
编辑:@sj26 是对的,您需要先运行它,然后才能调用后代:
Rails.application.eager_load!
这似乎对我有用:
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
Rails 仅在使用模型时才加载模型,因此 Dir.glob 行“需要”模型目录中的所有文件。
一旦您将模型放入数组中,您就可以做您想做的事情(例如在视图代码中):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
在Rails 6中,Zetiwerk成为了默认的代码加载器。
对于急切加载,请尝试:
Zeitwerk::Loader.eager_load_all
然后
ApplicationRecord.descendants
一行:Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
ActiveRecord::Base.connection.tables
仅一行:
ActiveRecord::Base.subclasses.map(&:name)
我还不能发表评论,但我认为sj26 答案应该是最佳答案。只是一个提示:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
是的,您可以通过多种方式找到所有模型名称,但我在 gem model_info中所做的是,它会为您提供所有模型,甚至包含在 gems 中。
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
这适用于 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
为避免预加载所有 Rails,您可以这样做:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency(f) 和Rails.application.eager_load!
使用的一样。这应该避免已经需要的文件错误。
然后你可以使用各种解决方案来列出 AR 模型,比如ActiveRecord::Base.descendants
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
这是一个经过复杂 Rails 应用程序审查的解决方案(为 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
它采用了该线程中答案的最佳部分,并将它们组合成最简单、最彻底的解决方案。这处理模型位于子目录中的情况,使用 set_table_name 等。
刚刚遇到这个,因为我需要打印所有模型及其属性(基于@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}"}}
这对我有用。特别感谢以上所有帖子。这应该返回所有模型的集合。
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
我在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
我把它放在一个初始化程序中,可以从任何地方调用它。防止不必要的鼠标使用。
Rails
实现方法,descendants
但模型不一定继承自ActiveRecord::Base
,例如,包含模块的类ActiveModel::Model
将具有与模型相同的行为,只是不会链接到表。
因此,作为上述同事所说的补充,只需稍加努力即可:
Class
Ruby类的 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
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
这将为您提供项目中的所有模型类。
可以检查这个
@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
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
假设所有模型都在 app/models 中,并且您的服务器上有 grep 和 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)
,即使它有点慢。
如果有人觉得它有用,我只是把这个例子放在这里。解决方案基于此答案https://stackoverflow.com/a/10712838/473040。
假设您有一个用作外部世界的主要 ID 的列(您可以在此处public_uid
找到为什么要这样做的原因)
现在假设您已经在一堆现有模型上引入了这个字段,现在您想要重新生成所有尚未设置的记录。你可以这样做
# 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
确保在调用之前预先加载您的应用程序,descendants
以便加载所有类:
Rails.application.eager_load! unless Rails.application.config.eager_load
ApplicationRecord.descendants.each do |clazz|
# do something with clazz, e.g. User, Event, Attendance, etc.
end