Apakah ada cara untuk mendapatkan koleksi semua Model di aplikasi Rails Anda?


201

Apakah ada cara agar Anda bisa mendapatkan koleksi semua Model di aplikasi Rails Anda?

Pada dasarnya, dapatkah saya melakukan hal-hal seperti: -

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

1
Jika Anda perlu mengumpulkan semua model termasuk model mesin Rails / railties, lihat jawabannya oleh @jaime
Andrei

Tidak bekerja di rel 5.1
aks

Jawaban:


98

EDIT: Lihatlah komentar dan jawaban lainnya. Ada jawaban yang lebih cerdas dari yang ini! Atau coba tingkatkan yang ini sebagai komunitas wiki.

Model tidak mendaftarkan diri ke objek master, jadi tidak, Rails tidak memiliki daftar model.

Tetapi Anda masih bisa melihat isi direktori model aplikasi Anda ...

Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
  # ...
end

EDIT: Gagasan lain (liar) adalah menggunakan refleksi Ruby untuk mencari setiap kelas yang memperluas ActiveRecord :: Base. Tidak tahu bagaimana Anda bisa mendaftar semua kelas ...

EDIT: Hanya untuk bersenang-senang, saya menemukan cara untuk membuat daftar semua kelas

Module.constants.select { |c| (eval c).is_a? Class }

EDIT: Akhirnya berhasil membuat daftar semua model tanpa melihat direktori

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

Jika Anda ingin menangani kelas turunan juga, maka Anda perlu menguji seluruh rantai superclass. Saya melakukannya dengan menambahkan metode ke kelas 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
FYI, saya mengatur waktu kedua metode hanya untuk bersenang-senang. Mencari direktori adalah urutan besarnya lebih cepat daripada mencari melalui kelas. Itu mungkin sudah jelas, tetapi sekarang Anda tahu :)
Edward Anderson

9
Juga, penting untuk dicatat bahwa mencari model melalui metode konstanta tidak akan menyertakan apa pun yang belum dirujuk sejak aplikasi dimulai, karena hanya memuat model sesuai permintaan.
Edward Anderson

4
Saya lebih suka 'Kernel.const_get constant_name' daripada 'eval constant_name'.
Jeremy Weathers

3
RAILS_ROOTtidak lagi tersedia di Rails 3. Sebagai gantinya, gunakanDir.glob(Rails.root.join('app/models/*'))
fanaugen

1
Sebenarnya, model memang mendaftarkan diri sebagai keturunan ActiveRecord::Basesekarang, jadi jika Anda ingin memuat semua model maka Anda dapat mengulanginya dengan mudah — lihat jawaban saya di bawah ini.
sj26

393

Seluruh jawaban untuk Rails 3, 4 dan 5 adalah:

Jika cache_classestidak aktif (secara default tidak aktif dalam pengembangan, tetapi dalam produksi):

Rails.application.eager_load!

Kemudian:

ActiveRecord::Base.descendants

Ini memastikan semua model dalam aplikasi Anda, terlepas dari di mana mereka berada, dimuat, dan permata apa pun yang Anda gunakan yang menyediakan model juga dimuat.

Ini juga harus bekerja pada kelas yang mewarisi dari ActiveRecord::Base, seperti ApplicationRecorddi Rails 5, dan hanya mengembalikan subtree dari keturunan:

ApplicationRecord.descendants

Jika Anda ingin tahu lebih banyak tentang bagaimana ini dilakukan, periksa ActiveSupport :: DescendantsTracker .


33
Luar biasa! Ini harus menjadi jawaban yang diterima. Untuk siapa pun yang menggunakan ini dalam tugas menyapu: Buat tugas Anda bergantung pada :environmentuntuk eager_load!bekerja.
Jo Liss

1
Atau, sebagai alternatif yang sedikit lebih cepat Rails.application.eager_load!, Anda dapat memuat model:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Ajedi32

5
@ Ajedi32 yang tidak lengkap, model dapat didefinisikan di luar direktori tersebut, terutama ketika menggunakan mesin dengan model. Sedikit lebih baik, setidaknya menggumpal semua Rails.paths["app/models"].existentdirektori. Dengan bersemangat memuat seluruh aplikasi adalah jawaban yang lebih lengkap dan akan memastikan tidak ada lagi yang tersisa bagi model untuk didefinisikan.
sj26

2
Saya mendapatkan arti sj26 tetapi mungkin ada sedikit kesalahan: sejauh yang saya tahu di lingkungan pengembangan cache_classes tidak aktif (false) itu sebabnya Anda perlu secara manual ingin memuat aplikasi untuk mengakses semua model. dijelaskan di sini
masciugo

3
@ Ajedi32 lagi, bukan jawaban lengkap. Jika Anda ingin hanya memuat model, cobalah:Rails.application.paths["app/models"].eager_load!
sj26

119

Kalau-kalau ada yang tersandung pada yang ini, saya punya solusi lain, tidak bergantung pada membaca dir atau memperluas kelas Kelas ...

ActiveRecord::Base.send :subclasses

Ini akan mengembalikan array kelas. Jadi Anda bisa melakukannya

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

8
mengapa Anda tidak menggunakan ActiveRecord::Base.subclassestetapi harus menggunakan send? Juga, sepertinya Anda harus "menyentuh" ​​model sebelum itu akan muncul, misalnya c = Category.newdan itu akan muncul. Kalau tidak, itu tidak akan terjadi.
nonopolaritas

52
Di Rails 3, ini telah diubah menjadiActiveRecord::Base.descendants
Tobias Cohen

3
Anda harus menggunakan "kirim" karena: anggota subclass dilindungi.
Kevin Rood

11
Terima kasih atas tip Rails 3. Bagi siapa pun yang datang, Anda masih perlu "menyentuh" ​​model-model tersebut sebelum ActiveRecord::Base.descendantsmencantumkannya.
nfm

3
Secara teknis di Rails 3 Anda memiliki subclass dan keturunan, semuanya memiliki arti berbeda.
sj26

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

akan kembali

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

Informasi tambahan Jika Anda ingin memanggil metode pada nama objek tanpa model: string metode tidak dikenal atau kesalahan variabel gunakan ini

model.classify.constantize.attribute_names

8
Ini akan memberi Anda semua tabel, bukan hanya model, karena beberapa tabel tidak selalu memiliki model terkait.
courtimas

Jawaban ini harus dianggap salah karena layak (dan umum dalam pengaturan warisan) untuk mengonfigurasi nama tabel menjadi sesuatu selain nama model yang di-pluralized. Jawaban ini memberikan jawaban yang benar bahkan ketika pengaturan menyimpang dari konfigurasi default.
lorefnon

dalam beberapa kasus ini bekerja lebih baik daripada ActiveRecord::Base.send :subclasses- mencari nama tabel adalah ide yang bagus. Secara otomatis menghasilkan nama-nama model mungkin bermasalah seperti yang disebutkan lorefnon.
Tilo

.capitalize.singularize.camelizedapat diganti menjadi .classify.
Maxim

34

Saya mencari cara untuk melakukan ini dan akhirnya memilih cara ini:

in the controller:
    @data_tables = ActiveRecord::Base.connection.tables

in the view:
  <% @data_tables.each do |dt|  %>
  <br>
  <%= dt %>
  <% end %>
  <br>

sumber: http://portfo.li/rails/348561-how-can-one-list-all-database-tables-from-one-project


1
Ini adalah satu-satunya cara saya bisa mendapatkan SEMUA model, termasuk model mesin Rails yang digunakan dalam aplikasi. Terima kasih atas tipnya!
Andrei

2
Beberapa metode yang berguna: ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}Beberapa model mungkin tidak diaktifkan karena itu Anda perlu menyelamatkannya.
Andrei

2
Adapting @ Andrei sedikit: model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Max Williams

30

Untuk model Rails5 sekarang menjadi subclass dari ApplicationRecordsehingga untuk mendapatkan daftar semua model dalam aplikasi Anda lakukan:

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

Atau lebih pendek:

ApplicationRecord.descendants.collect(&:name)

Jika Anda dalam mode dev, Anda harus berhasrat memuat model sebelum:

Rails.application.eager_load!

1
Saya menganggap bahwa ini akan mengharuskan kelas sudah dimuat dan akan memberikan hasil yang tidak lengkap dalam lingkungan pengembangan dengan autoloading diaktifkan. Saya tidak akan downvote tapi mungkin ini harus disebutkan dalam jawabannya.
lorefnon

tarif cukup, memperbarui
Nimir

Saya di Rails 6.0.2 dan eager_load! tidak membuat metode turunan untuk mengembalikan apa pun kecuali array kosong.
jgomo3

23

Saya pikir solusi @ hnovick adalah solusi yang keren jika Anda tidak memiliki model tabel-kurang. Solusi ini akan bekerja dalam mode pengembangan juga

Pendekatan saya agak berbeda -

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

classify seharusnya memberi Anda nama kelas dari sebuah string dengan benar . safe_constantize memastikan bahwa Anda dapat mengubahnya menjadi kelas dengan aman tanpa melemparkan pengecualian. Ini diperlukan jika Anda memiliki tabel database yang bukan model. kompak sehingga nil dalam enumerasi dihapus.


3
Itu mengagumkan @Aditya Sanghi. Saya tidak tahu safe_constantize.
lightyrs

Untuk rel 2.3.x, gunakan: ActiveRecord :: Base.connection.tables.map {| x | x.classify.constantize rescue nil} .compact
iheggie

@iheggie Umumnya lebih baik memposting itu sebagai jawaban terpisah daripada mengeditnya ke dalam posting yang ada.
Pokechu22

terima kasih, saya menemukan jawaban Anda paling cocok untuk saya #adiya
ilusionis

21

Jika Anda hanya menginginkan nama Kelas:

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

Jalankan saja di konsol Rails, tidak lebih. Semoga berhasil!

EDIT: @ sj26 benar, Anda harus menjalankan ini terlebih dahulu sebelum Anda dapat memanggil keturunan:

Rails.application.eager_load!

Apa yang saya inginkan. Terima kasih!
matahari terbit

menelepon mapdengan puts? Saya tidak mengerti maksudnyaActiveRecord::Base.descendants.map(&:model_name)
Nuno Costa

Anda dapat melakukannya dengan cara itu, tetapi mereka akan berada dalam satu array, bukan baris demi baris, dalam format yang jauh lebih mudah dibaca.
Jordan Michael Rushing

17

Ini sepertinya bekerja untuk saya:

  Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
  @models = Object.subclasses_of(ActiveRecord::Base)

Rails hanya memuat model ketika mereka digunakan, sehingga baris Dir.glob "membutuhkan" semua file dalam direktori model.

Setelah Anda memiliki model dalam array, Anda dapat melakukan apa yang Anda pikirkan (misalnya dalam kode tampilan):

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

Terima kasih bhousel. Saya awalnya pergi dengan gaya pendekatan ini tetapi akhirnya menggunakan solusi yang diposting Vincent di atas karena itu berarti bahwa saya tidak harus "memodelkan" nama file juga (yaitu menghapus setiap _, huruf kapital! Setiap kata dan kemudian bergabung mereka lagi).
mr_urf

dengan subdirektori:...'/app/models/**/*.rb'
artemave

Object.subclasses_of tidak digunakan lagi setelah v2.3.8.
David J.

11

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


7
Yang ini bagus karena, di Rails 3, model Anda tidak dimuat secara otomatis secara default, sehingga banyak metode di atas tidak akan mengembalikan semua model yang mungkin. Permutasi saya juga menangkap model dalam plugin dan subdirektori:Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
wbharding

2
@ wbharding Itu cukup bagus, tetapi kesalahan keluar ketika mencoba untuk menstabilkan nama-nama tes model rspec saya. ;-)
Ajedi32

@wbharding solusi bagus tetapi rusak ketika Anda memiliki model namespaced
Marcus Mansur

10

ActiveRecord::Base.connection.tables


Juga tindak lanjut yang bagus adalah <table_name> .column_names untuk mendaftar semua kolom dalam tabel. Jadi untuk tabel pengguna Anda, Anda akan menjalankan User.column_names
Mark Locklear

Ini akan memberi Anda semua tabel, bukan hanya model, karena beberapa tabel tidak selalu memiliki model terkait.
courtimas

7

Hanya dalam satu baris:

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

2
Itu tidak menunjukkan semua model untuk saya. Tidak yakin kenapa. Sebenarnya ini pasangan yang pendek.
courtimas

1
bekerja untukku. 'Hanya sedikit terlambat untuk menjawab itu saja. beri waktu.
boulder_ruby

2
Mungkin perlu Rails.application.eager_load!sebelum eksekusi dalam mode pengembangan.
denis.peplin

7

Saya belum bisa berkomentar, tapi saya pikir jawaban sj26 harus menjadi jawaban teratas. Hanya sebuah petunjuk:

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

6

Dengan Rails 6 , Zetiwerk menjadi pemuat kode default.

Untuk pemuatan yang cepat, cobalah:

Zeitwerk::Loader.eager_load_all

Kemudian

ApplicationRecord.descendants

5

Ya ada banyak cara Anda dapat menemukan semua nama model tetapi apa yang saya lakukan di permata model_info saya adalah, itu akan memberi Anda semua model bahkan termasuk dalam permata.

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

maka cukup cetak ini

@model_array

3

Ini berfungsi untuk 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

upvolt untuk Rails.application.eager_load! Ide
equivalent8

3

Untuk menghindari pra-muat semua Rails, Anda dapat melakukan ini:

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

require_dependency (f) adalah sama dengan yang Rails.application.eager_load!digunakan. Ini harus menghindari kesalahan file yang sudah diminta.

Kemudian Anda dapat menggunakan semua jenis solusi untuk mendaftar model AR, seperti ActiveRecord::Base.descendants


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

throws TypeError: tidak ada konversi tersirat dari Symbol menjadi String di konsol.
snowangel

1

Inilah solusi yang telah diperiksa dengan aplikasi Rails yang kompleks (Square yang memberi daya)

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

Dibutuhkan bagian terbaik dari jawaban di utas ini dan menggabungkannya dalam solusi paling sederhana dan menyeluruh. Ini menangani kasus di mana model Anda berada di subdirektori, gunakan set_table_name dll.


1

Baru saja menemukan yang ini, karena saya perlu mencetak semua model dengan atributnya (dibangun di atas komentar @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

Ini berhasil untuk saya. Terima kasih khusus untuk semua posting di atas. Ini akan mengembalikan koleksi semua model Anda.

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

The Railsalat metode descendants, tetapi model belum tentu pernah mewarisi dari ActiveRecord::Base, misalnya, kelas yang mencakup modul ActiveModel::Modelakan memiliki perilaku yang sama sebagai model, hanya tidak akan dihubungkan ke sebuah meja.

Jadi melengkapi apa yang dikatakan rekan-rekan di atas, upaya sekecil apa pun akan melakukan ini:

Patch monyet kelas ClassRuby:

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

dan metodenya models , termasuk leluhur, karena ini:

Metode Module.constantsmengembalikan (dangkal) koleksi symbols, bukan konstanta, jadi, metode Array#selectini dapat diganti seperti patch monyet ini dari 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

Patch monyet 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

Dan, akhirnya, metode model

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

Ini akan memberi Anda semua kelas model yang Anda miliki di proyek Anda.


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

Saya sudah mencoba banyak dari jawaban ini tidak berhasil di Rails 4 (wow mereka mengubah satu atau dua hal demi Tuhan) saya memutuskan untuk menambahkan sendiri. Yang memanggil ActiveRecord :: Base.connection dan menarik nama tabel berhasil tetapi tidak mendapatkan hasil yang saya inginkan karena saya menyembunyikan beberapa model (dalam folder di dalam app / models /) yang saya tidak ingin menghapus:

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

Saya meletakkannya di inisialisasi dan dapat memanggilnya dari mana saja. Mencegah penggunaan mouse yang tidak perlu.


0

dapat memeriksa ini

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

0

Dengan asumsi semua model ada di app / model dan Anda memiliki grep & awk di server Anda (sebagian besar kasus),

# 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")

Itu lebih cepat dari Rails.application.eager_load!atau perulangan melalui setiap file dengan Dir.

EDIT:

Kerugian dari metode ini adalah bahwa ia kehilangan model yang secara tidak langsung mewarisi dari ActiveRecord (misalnya FictionalBook < Book). Cara paling pasti adalah Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name), meskipun agak lambat.


0

Saya hanya melempar contoh ini ke sini jika ada yang merasa berguna. Solusi didasarkan pada jawaban ini https://stackoverflow.com/a/10712838/473040 .

Katakanlah Anda memiliki kolom public_uidyang digunakan sebagai ID utama untuk dunia luar (Anda dapat menemukan alasan mengapa Anda ingin melakukannya di sini) )

Sekarang katakanlah Anda telah memperkenalkan bidang ini pada sekelompok Model yang ada dan sekarang Anda ingin membuat kembali semua catatan yang belum ditetapkan. Anda bisa melakukannya seperti ini

# 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

Anda sekarang dapat berlari rake di:public_uids:generate

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.