有没有办法在Rails应用程序中获取所有模型的集合?

时间:2009-02-05 16:10:15

标签: ruby-on-rails activerecord collections model

有没有办法可以在Rails应用程序中获得所有模型的集合?

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

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

28 个答案:

答案 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

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