Railsアプリですべてのモデルのコレクションを取得する方法はありますか?


201

Railsアプリですべてのモデルのコレクションを取得する方法はありますか?

基本的に、私は次のようなことができますか?-

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

1
Railsエンジン/レールのモデルを含むすべてのモデルを収集する必要がある場合は、@ jaime
Andrei

レール5.1では機能しません
18年

回答:


98

編集:コメントや他の答えを見てください。これよりも賢い答えがあります!または、コミュニティ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

6
参考までに、私は楽しみのために両方の方法を計時しました。ディレクトリの検索は、クラスを検索するよりもはるかに高速です。それはおそらく明白でしたが、今ではわかります:)
エドワード・アンダーソン

9
また、定数メソッドを介してモデルを検索すると、アプリがオンデマンドでのみロードされるため、アプリの起動以降に参照されていないものは含まれないことに注意することが重要です。
エドワードアンダーソン

4
私は 'eval constant_name'より 'Kernel.const_get constant_name'を好む。
Jeremy Weathers、

3
RAILS_ROOTRails 3では使用できなくなりました。代わりに、次を使用してくださいDir.glob(Rails.root.join('app/models/*'))
fanaugen

1
実際、モデルはActiveRecord::Base現在の子孫として登録されているため、すべてのモデルを積極的にロードすると、簡単に反復できます。以下の私の回答を参照してください。
sj26 2012

393

Rails 3、4、および5の全体的な答えは次のとおりです。

cache_classesオフの場合(デフォルトでは開発ではオフですが、本番ではオンです):

Rails.application.eager_load!

次に:

ActiveRecord::Base.descendants

これにより、アプリケーションのすべてのモデルがどこにあるかに関係なく確実に読み込まれ、モデルを提供する使用中の宝石も読み込まれます。

これは、Rails 5のActiveRecord::BaseようApplicationRecordにから継承するクラスでも機能し、子孫のそのサブツリーのみを返します。

ApplicationRecord.descendants

これがどのように行われるかについてもっと知りたい場合は、ActiveSupport :: DescendantsTrackerをチェックしてください。


33
驚くばかり!これは受け入れられる答えになるはずです。レーキタスクでこれを使用しているユーザー:environment向け:タスクが機能するために依存するようにしますeager_load!
Jo Liss

1
または、わずかに速く代わりにとしてRails.application.eager_load!、あなただけのモデルをロードすることができますDir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Ajedi32

5
@ Ajedi32は完全ではありません。モデルでエンジンを使用する場合は特に、モデルをこれらのディレクトリの外部で定義できます。少し良いですが、少なくともすべてのRails.paths["app/models"].existentディレクトリをグロブします。アプリケーション全体を熱心にロードすることは、より完全な答えであり、モデルを定義するための場所が完全になくなることを確実にします。
sj26 2012年

2
私はsj26の意味を理解しましたが、おそらく小さな間違いがあるかもしれません。開発環境でcache_classesがオフ(false)である限り、すべてのモデルにアクセスするためにアプリケーションを手動で積極的にロードする必要があるのはそのためです。ここで説明
masciugo 2013年

3
@ Ajedi32は、完全な答えではありません。あなたが熱心な負荷のみのモデルにしたい場合は、試してみてくださいRails.application.paths["app/models"].eager_load!
sj26

119

誰かがこれにつまずく場合に備えて、クラスのクラスの読み取りまたは拡張に依存しない別の解決策があります...

ActiveRecord::Base.send :subclasses

これはクラスの配列を返します。だからあなたはそれから

ActiveRecord::Base.send(:subclasses).map(&:name)

8
なぜあなたは使用しませんActiveRecord::Base.subclassesが、使用する必要がありますかsend?また、たとえばc = Category.new、表示される前にモデルを「タッチ」する必要があるようです。そうでなければ、それはしません。
非極性

52
レール3には、これは、に変更されましたActiveRecord::Base.descendants
トビアスコーエン

3
:subclassesメンバーが保護されているため、「送信」を使用する必要があります。
Kevin Rood、2011

11
Rails 3のヒントをありがとう。他の人が一緒に来る場合は、モデルActiveRecord::Base.descendantsをリストする前に、まだモデルに「触れる」必要があります。
nfm

3
技術的にはRails 3 ではサブクラス子孫があり、それらは異なる意味を持っています。
sj26

67
ActiveRecord::Base.connection.tables.map do |model|
  model.capitalize.singularize.camelize
end

戻ります

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

追加情報 model:string不明なメソッドまたは変数エラーなしでオブジェクト名のメソッドを呼び出したい場合は、これを使用してください。

model.classify.constantize.attribute_names

8
ただし、一部のテーブルには常にモデルが関連付けられているとは限らないため、モデルだけでなくすべてのテーブルが表示されます。
コートシマ

テーブルの名前をモデルの複数化された名前以外の名前に構成することは可能であり(レガシーセットアップでは一般的です)、この回答は正しくないと考えるべきです。この回答は、セットアップがデフォルトの構成から外れている場合でも正しい回答を提供します。
lorefnon 2016年

場合によっては、これよりもうまく機能するActiveRecord::Base.send :subclassesことがあります。テーブル名を探すことをお勧めします。lorefnonが言及しているように、モデル名の自動生成は問題があるかもしれません。
Tilo

.capitalize.singularize.camelizeに置き換えることができます.classify
マキシム

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


1
これが、アプリで使用されているRailsエンジンのモデルを含むすべてのモデルを取得できる唯一の方法です。先端をありがとう!
アンドレイ

2
いくつかの便利な方法:ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}一部のモデルがアクティブ化されていない可能性があるため、レスキューする必要があります。
Andrei

2
@Andreiの適応は少し:model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
マックスウィリアムズ

30

Rails5モデルは今サブクラスApplicationRecordようにあなたがあなたのアプリケーション内のすべてのモデルのリストを取得します:

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

またはより短い:

ApplicationRecord.descendants.collect(&:name)

開発モードの場合は、事前にモデルを熱心にロードする必要があります。

Rails.application.eager_load!

1
これは、クラスがすでにロードされている必要があり、自動ロードが有効になっている開発環境では不完全な結果をもたらすと考えています。私は反対投票はしませんが、おそらくこれは答えで言及されるべきです。
lorefnon 2016年

十分な運賃で更新
Nimir

Rails 6.0.2とeager_loadを使用しています!空の配列以外を返すように子孫メソッドを作成しませんでした。
jgomo3

23

テーブルレスモデルがない場合、@ hnovickのソリューションはクールなソリューションだと思います。このソリューションは開発モードでも機能します

私のアプローチは微妙に異なりますが-

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

classifyは、文字列からクラスの名前を適切に与えることを想定しています。safe_constantizeは、例外をスローせずにクラスを安全にクラスに変換できることを保証します。これは、モデルではないデータベーステーブルがある場合に必要です。列挙型のnilが削除されるようにコンパクトにします。


3
@Aditya Sanghiは素晴らしいですね。知りませんでしたsafe_constantize
lightyrs 2012

Rails 2.3.xの場合は、次を使用します。ActiveRecord:: Base.connection.tables.map {| x | x.classify.constantize rescue nil} .compact
iheggie

@iheggie一般に、既存の投稿に編集するよりも、別の回答として投稿する方が適切です。
Pokechu22 2014年

おかげで、私はあなたが私が#adiyaに最適答えた
イリュージョニスト

21

クラス名だけが必要な場合:

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

Railsコンソールで実行するだけです。幸運を!

編集:@ sj26は正しいです、子孫を呼び出す前にこれを最初に実行する必要があります:

Rails.application.eager_load!

ちょうど私が欲しかったもの。感謝!
sunsations 14年

呼び出すmapputs?私はポイントがあるべき得ることはありませんActiveRecord::Base.descendants.map(&:model_name)
ヌーノ・コスタ

そうすることもできますが、1行ずつではなく1つの配列になり、非常に読みやすい形式になります。
ジョーダンマイケルラッシング

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 %>

ありがとうbhousel。私はもともとこのスタイルのアプローチを採用していましたが、Vincentが上記で投稿したソリューションを使用することになりました。ファイル名も「モデル化」する必要がないためです(つまり、_を取り除き、大文字にして各単語を結合します)。それらを再び)。
mr_urf 2009

サブディレクトリあり:...'/app/models/**/*.rb'
artemave '16 / 03/11

Object.subclasses_ofは、v2.3.8以降は非推奨です。
David J.

11

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


7
Rails 3ではモデルがデフォルトで自動ロードされないため、上記のメソッドの多くがすべての可能なモデルを返すわけではないため、これは素晴らしいものです。私の順列はまた、プラグインとサブディレクトリのモデルをキャプチャします:Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
wbharding '25

2
@wbhardingかなりいいですが、rspecモデルテストの名前を定数化しようとするとエラーになります。;-)
Ajedi32 2012

@wbhardingは優れたソリューションですが、名前空間のあるモデルがあると機能しません
Marcus Mansur

10

ActiveRecord::Base.connection.tables


また、テーブルのすべての列をリストする<table_name> .column_namesも便利です。したがって、ユーザーテーブルでは、User.column_namesを実行します
Mark Locklear

ただし、一部のテーブルには常にモデルが関連付けられているとは限らないため、モデルだけでなくすべてのテーブルが表示されます。
コートシマ

7

たった1行で:

 ActiveRecord::Base.subclasses.map(&:name)

2
これですべてのモデルが表示されるわけではありません。なぜだかわかりません。実際、それは数個の短いものです。
コートシマ

1
私のために働いた。「それですべてに答えるために少し遅れました。時間をかけてください。
boulder_ruby 2013年

2
おそらくRails.application.eager_load!開発モードで実行する前に必要です。
denis.peplin 2015

7

まだコメントはできませんが、sj26が一番良いと思います。ほんのヒント:

Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants

6

Railsの6Zetiwerkデフォルトのコード・ローダになりました。

熱心にロードするには、次を試してください:

Zeitwerk::Loader.eager_load_all

その後

ApplicationRecord.descendants

5

はい、すべてのモデル名を見つける方法はたくさんありますが、gem_model_infoで私がしたことは、gemに含まれているすべてのモデルを提供します。

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

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

そのRails.application.eager_loadのupvolt!アイデア
同等

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



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などを使用します。


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}"}}

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

1

Rails実装方法descendantsは必ずしもこれまでから継承されないが、モデルActiveRecord::Baseモジュールを含み、例えば、クラス、ActiveModel::Modelモデルと同じ動作をしますが、ちょうどテーブルにリンクされますされません。

上記の同僚の言うことを補足すると、わずかな労力でこれを実行できます。

ClassRuby のクラスのモンキーパッチ:

class Class
  def extends? constant
    ancestors.include?(constant) if constant != self
  end
end

そして、このようにmodels、祖先を含むメソッド:

このメソッドModule.constantssymbols定数の代わりに(見かけ上)のコレクションを返すので、このメソッド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

そして最後に、modelsメソッド

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

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

これにより、プロジェクトにあるすべてのモデルクラスが提供されます。


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

0

私はRails 4でこれらの回答の多くを失敗に終わったので(すごいために1つまたは2つ変更したので)、自分で追加することにしました。ActiveRecord :: Base.connectionを呼び出してテーブル名をプルしたものは機能しましたが、(app / models /内のフォルダーにある)いくつかのモデルを非表示にしているため、希望する結果が得られませんでした削除:

def list_models
  Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end

それを初期化子に入れて、どこからでも呼び出すことができます。不必要なマウスの使用を防ぎます。


0

これを確認できます

@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}

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)少し遅いですが、最も確実な方法はです。


0

誰かがそれが便利だと思ったら、私はこの例をここに投げます。ソリューションはこの回答https://stackoverflow.com/a/10712838/473040に基づいています

public_uid外部の世界へのプライマリIDとして使用される列があるとしましょう(ここでそれを実行する理由を見つけることができます

ここで、既存のモデルの束にこのフィールドを導入し、まだ設定されていないすべてのレコードを再生成するとします。このようにできます

# 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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.