223

有没有一种方法可以收集 Rails 应用程序中的所有模型?

基本上,我可以这样做: -

Models.each do |model|
  puts model.class.name
end
4

29 回答 29

422

Rails 3、4 和 5 的完整答案是:

如果cache_classes关闭(默认情况下它在开发中关闭,但在生产中打开):

Rails.application.eager_load!

然后:

ActiveRecord::Base.descendants

这可以确保您的应用程序中的所有模型(无论它们在哪里)都被加载,并且您正在使用的提供模型的任何 gem 也被加载。

这也应该适用于继承自的类ActiveRecord::Base,例如ApplicationRecord在 Rails 5 中,并且只返回后代的子树:

ApplicationRecord.descendants

如果您想了解有关如何完成此操作的更多信息,请查看ActiveSupport::DescendantsTracker

于 2012-05-23T02:52:32.927 回答
120

以防万一有人偶然发现这个,我有另一种解决方案,不依赖于 dir 阅读或扩展 Class 类......

ActiveRecord::Base.send :subclasses

这将返回一个类数组。所以你可以这样做

ActiveRecord::Base.send(:subclasses).map(&:name)
于 2010-01-12T10:50:42.427 回答
103

编辑:查看评论和其他答案。有比这个更聪明的答案!或者尝试将其改进为社区 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
于 2009-02-05T16:17:39.543 回答
70
ActiveRecord::Base.connection.tables.map do |model|
  model.capitalize.singularize.camelize
end

将返回

["Article", "MenuItem", "Post", "ZebraStripePerson"]

附加信息如果您想在没有模型的对象名称上调用方法:字符串未知方法或变量错误,请使用此

model.classify.constantize.attribute_names
于 2011-12-08T21:34:29.690 回答
39

对于Rails5模型现在ApplicationRecordso 的子类,要获取应用程序中所有模型的列表,您可以:

ApplicationRecord.descendants.collect { |type| type.name }

或更短:

ApplicationRecord.descendants.collect(&:name)

如果您处于开发模式,则需要先预先加载模型:

Rails.application.eager_load!
于 2016-03-29T07:06:33.397 回答
34

我寻找方法来做到这一点,最终选择了这种方式:

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

于 2011-01-04T20:30:47.100 回答
23

如果您没有无表模型,我认为@hnovick 的解决方案是一个很酷的解决方案。该解决方案也可以在开发模式下工作

不过,我的方法略有不同-

ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact

分类应该可以正确地从字符串中为您提供类的名称。safe_constantize 确保您可以安全地将其转换为类而不会引发异常。如果您有不是模型的数据库表,则需要这样做。紧凑,以便删除枚举中的任何 nil。

于 2012-05-21T07:27:02.543 回答
21

如果你只想要类名:

ActiveRecord::Base.descendants.map {|f| puts f}

只需在 Rails 控制台中运行它,仅此而已。祝你好运!

编辑:@sj26 是对的,您需要先运行它,然后才能调用后代:

Rails.application.eager_load!
于 2014-01-05T05:28:39.723 回答
17

这似乎对我有用:

  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 %>
于 2009-02-06T16:49:19.587 回答
15

Rails 6中,Zetiwerk成为了默认的代码加载器。

对于急切加载,请尝试:

Zeitwerk::Loader.eager_load_all

然后

ApplicationRecord.descendants
于 2019-10-08T15:58:53.337 回答
11

一行:Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }

于 2010-03-11T15:42:38.410 回答
11

ActiveRecord::Base.connection.tables

于 2012-04-16T14:35:09.423 回答
7

仅一行:

 ActiveRecord::Base.subclasses.map(&:name)
于 2012-07-19T14:08:58.493 回答
7

我还不能发表评论,但我认为sj26 答案应该是最佳答案。只是一个提示:

Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
于 2013-11-13T15:38:12.540 回答
7

是的,您可以通过多种方式找到所有模型名称,但我在 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
于 2017-04-28T06:22:26.753 回答
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
于 2014-07-26T22:34:02.370 回答
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

于 2017-03-28T15:48:42.610 回答
2
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
于 2012-03-27T12:14:33.503 回答
1

这是一个经过复杂 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 等。

于 2013-06-10T12:41:12.697 回答
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}"}}
于 2013-10-23T06:24:36.930 回答
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
于 2013-11-13T23:02:44.320 回答
1

我在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

我把它放在一个初始化程序中,可以从任何地方调用它。防止不必要的鼠标使用。

于 2013-11-14T22:25:07.780 回答
1

Rails实现方法,descendants但模型不一定继承自ActiveRecord::Base,例如,包含模块的类ActiveModel::Model将具有与模型相同的行为,只是不会链接到表。

因此,作为上述同事所说的补充,只需稍加努力即可:

ClassRuby类的 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
于 2016-10-16T21:21:56.190 回答
1
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

这将为您提供项目中的所有模型类。

于 2018-11-22T18:35:57.480 回答
0

可以检查这个

@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
于 2013-05-03T10:55:08.397 回答
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
于 2013-08-22T05:29:45.000 回答
0

假设所有模型都在 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),即使它有点慢。

于 2015-10-08T21:15:59.320 回答
0

如果有人觉得它有用,我只是把这个例子放在这里。解决方案基于此答案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

于 2016-10-06T09:57:36.557 回答
0

确保在调用之前预先加载您的应用程序,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
于 2021-11-13T14:30:44.240 回答