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
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
Jawaban:
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
RAILS_ROOT
tidak lagi tersedia di Rails 3. Sebagai gantinya, gunakanDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
sekarang, jadi jika Anda ingin memuat semua model maka Anda dapat mengulanginya dengan mudah — lihat jawaban saya di bawah ini.
Seluruh jawaban untuk Rails 3, 4 dan 5 adalah:
Jika cache_classes
tidak 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 ApplicationRecord
di Rails 5, dan hanya mengembalikan subtree dari keturunan:
ApplicationRecord.descendants
Jika Anda ingin tahu lebih banyak tentang bagaimana ini dilakukan, periksa ActiveSupport :: DescendantsTracker .
:environment
untuk eager_load!
bekerja.
Rails.application.eager_load!
, Anda dapat memuat model:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
direktori. Dengan bersemangat memuat seluruh aplikasi adalah jawaban yang lebih lengkap dan akan memastikan tidak ada lagi yang tersisa bagi model untuk didefinisikan.
Rails.application.paths["app/models"].eager_load!
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)
ActiveRecord::Base.subclasses
tetapi harus menggunakan send
? Juga, sepertinya Anda harus "menyentuh" model sebelum itu akan muncul, misalnya c = Category.new
dan itu akan muncul. Kalau tidak, itu tidak akan terjadi.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
mencantumkannya.
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
ActiveRecord::Base.send :subclasses
- mencari nama tabel adalah ide yang bagus. Secara otomatis menghasilkan nama-nama model mungkin bermasalah seperti yang disebutkan lorefnon.
.capitalize.singularize.camelize
dapat diganti menjadi .classify
.
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
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.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Untuk model Rails5 sekarang menjadi subclass dari ApplicationRecord
sehingga 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!
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.
safe_constantize
.
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!
map
dengan puts
? Saya tidak mengerti maksudnyaActiveRecord::Base.descendants.map(&:model_name)
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 %>
...'/app/models/**/*.rb'
Pada satu baris: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
ActiveRecord::Base.connection.tables
Hanya dalam satu baris:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
sebelum eksekusi dalam mode pengembangan.
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
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
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
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
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.
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}"}}
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
The Rails
alat metode descendants
, tetapi model belum tentu pernah mewarisi dari ActiveRecord::Base
, misalnya, kelas yang mencakup modul ActiveModel::Model
akan 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 Class
Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
dan metodenya models
, termasuk leluhur, karena ini:
Metode Module.constants
mengembalikan (dangkal) koleksi symbols
, bukan konstanta, jadi, metode Array#select
ini 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
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
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.
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.
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_uid
yang 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