web-dev-qa-db-ja.com

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

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

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

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

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

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

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

Rails.application.eager_load!

次に:

ActiveRecord::Base.descendants

これにより、アプリケーションのすべてのモデルがどこにあるかに関係なくロードされ、モデルを提供する使用中のGemもロードされます。

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

ApplicationRecord.descendants

howの詳細を知りたい場合は、 ActiveSupport :: DescendantsTracker をご覧ください。

368
sj26

誰かがこれに遭遇した場合に備えて、ディレクトリの読み取りやクラスクラスの拡張に依存せずに、別の解決策があります...

ActiveRecord::Base.send :subclasses

これはクラスの配列を返します。だからあなたはそうすることができます

ActiveRecord::Base.send(:subclasses).map(&:name)
114
kikito
ActiveRecord::Base.connection.tables.map do |model|
  model.capitalize.singularize.camelize
end

戻ります

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

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

model.classify.constantize.attribute_names
65
lightyrs

私はこれを行う方法を探して、この方法を選択することになりました:

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

33
jaime

Rails5モデルの場合 現在はサブクラス of ApplicationRecordので、アプリ内のすべてのモデルのリストを取得するには:

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

または短く:

ApplicationRecord.descendants.collect(&:name)

開発モードの場合、次の前にモデルを積極的にロードする必要があります。

Rails.application.eager_load!
24
Nimir

@hnovickのソリューションは、テーブルなしのモデルを持っていなければクールだと思います。このソリューションは開発モードでも機能します

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

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

classifyは、文字列properlyからクラスの名前を提供することになっています。 safe_constantizeを使用すると、例外をスローせずに安全にクラスに変換できます。これは、モデルではないデータベーステーブルがある場合に必要です。列挙体のすべてのnilが削除されるようにコンパクト。

23
Aditya Sanghi

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

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ディレクトリ内のすべてのファイルを「必要」とします。

モデルを配列に配置したら、考えていたことを実行できます(コードの表示など)。

<% @models.each do |v| %>
  <li><%= h v.to_s %></li>
<% end %>
17
bhousel

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

11
vjt

ActiveRecord::Base.connection.tables

9
Mark Locklear

たった1行で:

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

まだコメントできませんが、 sj26 answer が一番の答えだと思います。ちょっとしたヒント:

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

はい、すべてのモデル名を見つける方法はたくさんありますが、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
5
nitanshu verma

これは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
3
ryan0

すべてのRailsのプリロードを回避するには、次のようにします。

Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }

require_dependency(f)は、Rails.application.eager_load!が使用するものと同じです。これにより、すでに必要なファイルエラーを回避できます。

その後、あらゆる種類のソリューションを使用して、ActiveRecord::Base.descendantsなどのARモデルをリストできます。

3
John Owen Chile
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
2
Naveed

これは私のために働いた。上記のすべての投稿に感謝します。これにより、すべてのモデルのコレクションが返されます。

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
Kevin

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#selectModuleのこのモンキーパッチのように置き換えることができます。

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
1
rplaurindo

以下は、複雑な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
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

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

1
Victor

すべてのモデルをその属性(@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
gouravtiwari21

すべてのモデルがアプリ/モデルにあり、サーバーに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
konyak

これを確認できます

@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
0
Arvind
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
Abdul

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
boulder_ruby

誰かが役に立つと思うなら、私はこの例をここに投げています。ソリューションはこの答えに基づいています https://stackoverflow.com/a/10712838/47304

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を実行できるようになりました

0
equivalent8