Rails uygulamanızdaki tüm Modellerin bir koleksiyonunu almanın bir yolu var mı?


202

Rails uygulamanızdaki tüm Modellerin bir koleksiyonunu almanın bir yolu var mı?

Temel olarak, beğenileri yapabilir miyim: -

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

1
Eğer tarafından cevap görmek, Raylar motorları / railties modelleri dahil tüm modelleri toplamak gerekirse @jaime
Andrei

Raylarda çalışmaz 5.1
aks

Yanıtlar:


98

DÜZENLEME: Yorumlara ve diğer yanıtlara bakın. Bundan daha akıllı cevaplar var! Veya bunu topluluk wiki'si olarak geliştirmeye çalışın.

Modeller kendilerini bir ana nesneye kaydetmezler, bu nedenle hayır, Rails modellerin listesine sahip değildir.

Ancak yine de uygulamanızın modeller dizininin içeriğine bakabilirsiniz ...

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

EDIT: Başka (vahşi) bir fikir, ActiveRecord :: Base genişleten her sınıfı aramak için Ruby yansıması kullanmak olacaktır. Tüm sınıfları nasıl listeleyebileceğinizi bilmiyorum ...

EDIT: Sadece eğlence için, tüm sınıfları listelemek için bir yol buldum

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

EDIT: Sonunda dizinlere bakmadan tüm modelleri listeleme başarılı

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

Eğer türetilmiş sınıfı da ele almak istiyorsanız, tüm üst sınıf zincirini test etmeniz gerekecektir. Class sınıfına bir yöntem ekleyerek yaptım:

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
Bilginize, her iki yöntemi de sadece eğlence için zamanladım. Dizinlere bakmak, sınıfları aramaktan daha hızlı bir büyüklük sırasıdır. Muhtemelen açıktı, ama şimdi biliyorsunuz :)
Edward Anderson

9
Ayrıca, sabitler yöntemleriyle model aramanın, uygulama başlatıldığından beri referans verilmeyen hiçbir şey içermeyeceğini, yalnızca modelleri istek üzerine yüklediğini unutmayın.
Edward Anderson

4
'Kernel.const_get sabit_adı' yerine 'sabit_adı' değerini tercih ederim.
Jeremy Weathers

3
RAILS_ROOTartık Rails 3'te mevcut değil. Bunun yerine, kullanınDir.glob(Rails.root.join('app/models/*'))
fanaugen

1
Aslında, modeller kendilerini şimdinin torunları olarak kaydediyorlar ActiveRecord::Base, bu yüzden tüm modelleri yüklerseniz, bunları kolayca yineleyebilirsiniz - aşağıdaki cevabımı görün.
sj26

399

Ray 3, 4 ve 5'in cevabı:

Eğer cache_classeskapalıysa (varsayılan olarak üretimde üzerinde gelişiminde kapalı, ama):

Rails.application.eager_load!

Sonra:

ActiveRecord::Base.descendants

Bu, uygulamanızdaki tüm modellerin, nerede olduklarına bakılmaksızın, yüklendiğinden ve kullandığınız mücevher sağlayan modellerin de yüklendiğinden emin olmanızı sağlar.

Bu , Rails 5'teki ActiveRecord::Basegibi miras alan ApplicationRecordve yalnızca o torun alt ağacını döndüren sınıflar üzerinde de çalışmalıdır :

ApplicationRecord.descendants

Bunun nasıl yapıldığı hakkında daha fazla bilgi edinmek isterseniz ActiveSupport :: DescendantsTracker'a göz atın .


33
Müthiş! Bu kabul edilen cevap olmalı. Bir tırmık görev bu kullanarak herkes için: Make görev bağlıdır :environmentiçin eager_load!işe.
Jo Liss

1
Veya, biraz daha hızlı bir alternatif olarak Rails.application.eager_load!, sadece modelleri yükleyebilirsiniz:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Ajedi32

5
@ Ajedi32 tam olmayan modeller, özellikle modelli motorlar kullanılırken bu dizinlerin dışında tanımlanabilir. Biraz daha iyi, en azından tüm Rails.paths["app/models"].existentdizinleri topla . Tüm uygulamayı yüklemek istekli olmak daha eksiksiz bir cevaptır ve modellerin tanımlanması için kesinlikle hiçbir yer kalmamasını sağlayacaktır.
sj26

2
Sj26 ne demek var ama belki küçük bir hata var: geliştirme ortamında bildiğim kadarıyla cache_classes kapalı (yanlış) bu yüzden manuel olarak tüm modellere erişmek için uygulamayı yüklemek için istekli gerekir. burada açıkladı
masciugo

3
@ Ajedi32 tekrar, tam cevap değil. Sadece modelleri yüklemek istiyorsanız o zaman deneyin:Rails.application.paths["app/models"].eager_load!
sj26 29:13

119

Herkesin bu konuda tökezlemesi durumunda, Dir sınıfını okumaya veya Class sınıfını genişletmeye dayanmayan başka bir çözümüm var ...

ActiveRecord::Base.send :subclasses

Bu bir dizi sınıf döndürür. Böylece yapabilirsin

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

8
neden kullanmıyorsun ActiveRecord::Base.subclassesama kullanmak zorundasın send? Ayrıca, görünmeden önce modele "dokunmanız" gibi görünüyor, örneğin c = Category.newve görünecek. Aksi halde olmayacak.
nonopolarite

52
Rails 3'te bu şu şekilde değiştirildiActiveRecord::Base.descendants
Tobias Cohen

3
"Send" seçeneğini kullanmalısınız çünkü: subclasses üyesi korunmaktadır.
Kevin Rood

11
Rails 3 bahşiş için teşekkürler. Gelen herkes için, daha önce ActiveRecord::Base.descendantslistelemek için modellere "dokunmanız" gerekir.
nfm

3
Teknik olarak Rails 3'te alt sınıflarınız ve torunlarınız var, bunlar farklı şeyler ifade ediyor.
sj26

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

dönecek

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

Ek bilgiler Nesne adında model olmadan bir yöntem çağırmak istiyorsanız: string unknown method veya değişken hataları bunu kullanın

model.classify.constantize.attribute_names

8
Bazı tablolar her zaman ilişkili modellere sahip olmadığından, bu sadece tüm modelleri değil, tüm tabloları alacaktır.
courtsimas

Bu yanıt, tablonun adını modelin çoğullaştırılmış adından başka bir şey olacak şekilde yapılandırmanın mümkün olduğu (ve eski kurulumlarda yaygın olduğu) için yanlış kabul edilmelidir. Bu yanıt , kurulum varsayılan yapılandırmadan farklı olsa bile doğru yanıtı verir.
lorefnon

bazı durumlarda bu daha iyi çalışır ActiveRecord::Base.send :subclasses- tablo adlarını aramak iyi bir fikirdir. Model adlarının otomatik olarak oluşturulması, belirtildiği gibi sorunlu olabilir.
Tilo

.capitalize.singularize.camelizeile değiştirilebilir .classify.
Maxim

34

Bunu yapmanın yollarını aradım ve bu yolu seçtim:

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

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

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


1
Uygulamada kullanılan Rails motorlarının modelleri de dahil olmak üzere TÜM modelleri alabilmemin tek yolu budur. Bahşiş için teşekkürler!
Andrei

2
Birkaç kullanışlı yöntem: ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}Bazı modeller etkinleştirilmeyebilir, bu nedenle onu kurtarmanız gerekir.
Andrei

2
@ Andrei'nin biraz uyarlanması: model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Max Williams

30

İçin Rails5 modelleri şimdi alt sınıfların arasında ApplicationRecordyapmanız uygulamanızdaki tüm modellerin listesini almak çok:

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

Veya daha kısa:

ApplicationRecord.descendants.collect(&:name)

Dev modundaysanız, daha önce yük modelleri istekli olmanız gerekir:

Rails.application.eager_load!

1
Bu sınıflar zaten yüklü olmasını gerektirir ve otomatik yükleme etkin ile geliştirme ortamında eksik sonuçlar verecek. Ben aşağılamak olmaz ama belki bu cevabında belirtilmelidir.
lorefnon

yeterince ücret, güncelleme
Nimir

Rails 6.0.2 ve eager_load kullanıyorum! boş bir dizi dışında bir şey döndürmek için torunları yöntemi yapmadı.
jgomo3

23

Ben tablo olmadan modelleri yoksa @ hnovick çözüm harika bir çözüm olduğunu düşünüyorum. Bu çözüm geliştirme modunda da işe yarar

Benim yaklaşımım çok farklı olsa da -

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

classify'ın bir dizeden uygun şekilde sınıfın adını vermesi iyi olur . safe_constantize, bir istisna atmadan güvenle bir sınıfa dönüştürmenizi sağlar. Model olmayan veritabanı tablolarınız olması durumunda bu gereklidir. numaralandırmalı böylece numaralandırmadaki nils çıkarılır.


3
Bu harika @Aditya Sanghi. Bilmiyordum safe_constantize.
lightyrs

2.3.x rayları için şunu kullanın: ActiveRecord :: Base.connection.tables.map {| x | x.classify.constantize ressil nil} .compact
iheggie

@iheggie Bunu ayrı bir cevap olarak göndermek, mevcut gönderiyi düzenlemekten daha iyidir.
Pokechu22

teşekkürler, benim için en uygun cevabı buldum #adiya
illüzyonist

21

Yalnızca Sınıf adlarını istiyorsanız:

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

Sadece Rails konsolunda çalıştırın, başka bir şey değil. İyi şanslar!

EDIT: @ sj26 doğru, torunları aramak için önce bunu çalıştırmanız gerekir:

Rails.application.eager_load!

Tam istediğim gibi. Teşekkür!
sunsations

çağırma mapile puts? Olması gereken noktayı anlamıyorumActiveRecord::Base.descendants.map(&:model_name)
Nuno Costa

Bunu bu şekilde yapabilirsiniz, ancak okunması daha kolay bir biçimde satır satır yerine tek bir dizide olacaklardır.
Jordan Michael Rushing

17

Bu benim için işe yarıyor gibi görünüyor:

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

Rails modelleri yalnızca kullanıldıklarında yükler, bu nedenle Dir.glob satırı modeller dizinindeki tüm dosyaları "gerektirir".

Bir dizideki modelleri aldıktan sonra, düşündüğünüzü yapabilirsiniz (örneğin, görünüm kodunda):

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

Teşekkürler bhousel. Başlangıçta bu tarz bir yaklaşımla gittim ancak Vincent'ın dosya adını da "Modellemek" zorunda olmadığım anlamına geldiği için yukarıda yayınladığı çözümü kullandım (yani herhangi bir _'i çıkar, her kelimeyi büyük harfle yaz ve sonra katıl! yine onlar).
mr_urf

alt dizinlerle:...'/app/models/**/*.rb'
artemave

Object.subclasses_of v2.3.8'den sonra kullanımdan kaldırılmıştır.
David J.

11

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


7
Rails 3'te, modelleriniz varsayılan olarak otomatik olarak yüklenmediğinden, bu iyi bir şeydir, bu nedenle yukarıdaki yöntemlerin çoğu olası tüm modelleri döndürmez. Benim permütasyon eklentiler ve alt dizinler de modelleri yakalar:Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
wbharding

2
@wbharding Bu çok hoş, ama benim rspec model testlerimin isimlerini sabitlemeye çalıştığında hata veriyor. ;-)
Ajedi32

@wbharding güzel bir çözüm ama isim aralıklı modeller olduğunda kırılıyor
Marcus Mansur

10

ActiveRecord::Base.connection.tables


Ayrıca, tablodaki tüm sütunları listelemek için <table_name> .column_names güzel bir takiptir. Yani kullanıcı tablonuz için User.column_names
Mark Locklear

Bazı tablolar her zaman ilişkili modellere sahip olmadığından, bu sadece tüm modelleri değil, tüm tabloları alacaktır.
courtsimas

7

Sadece bir satırda:

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

2
Bu benim için tüm modelleri göstermiyor. Emin değilim neden. Aslında birkaç kısa.
courtsimas

1
benim için çalıştı. Hepsini cevaplamak için biraz geç. zaman ver.
boulder_ruby

2
Muhtemelen Rails.application.eager_load!geliştirme modunda yürütülmeden önce ihtiyacı vardır .
denis.peplin

7

Henüz yorum yapamam, ancak sj26 cevabının en önemli cevap olduğunu düşünüyorum . Sadece bir ipucu:

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

6

İle Raylar 6 , Zetiwerk varsayılan kod yükleyici haline geldi.

İstekli yükleme için şunu deneyin:

Zeitwerk::Loader.eager_load_all

Sonra

ApplicationRecord.descendants

5

Evet, tüm model adlarını bulabilmenin birçok yolu var ama benim mücevher model_info'mda yaptığım şey, mücevherlere dahil olan tüm modelleri size verecek.

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

o zaman bunu yazdır

@model_array

3

Bu Rails 3.2.18 için çalışır

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 için yükseltin! Fikir
equivalent8

3

Tüm Rayları önceden yüklemekten kaçınmak için şunları yapabilirsiniz:

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

gereksinim_ bağımlılığı (f), Rails.application.eager_load! . Bu, gerekli dosya hatalarından kaçınmalıdır.

Ardından AR modellerini listelemek için her türlü çözümü kullanabilirsiniz. ActiveRecord::Base.descendants


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

TypeError atar: Konsoldaki Symbol'in String'e örtük dönüşümü yoktur.
snowangel

1

İşte karmaşık bir Rails uygulaması (bir güçlendirme Meydanı) ile incelenmiş bir çözüm

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

Bu konudaki cevapların en iyi kısımlarını alır ve bunları en basit ve en kapsamlı çözümde birleştirir. Bu, modellerinizin alt dizinlerde olduğu durumlarda, set_table_name vb. Kullanın.


1

Tüm modelleri kendi nitelikleriyle (@Aditya Sanghi'nin yorumuna dayanarak) yazdırmam gerektiğinden, bununla karşılaştım:

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

Bu benim için çalıştı. Yukarıdaki tüm gönderilere özel teşekkürler. Bu, tüm modellerinizin bir koleksiyonunu döndürmelidir.

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

RailsUygular yöntem descendantszorunlu hiç devralan, ama modeller ActiveRecord::Basemodülü içerir, örneğin, sınıfActiveModel::Model bir model olarak aynı davranışı sahip olacak, sadece bir tabloya bağlantılı olacak etmez.

Yukarıdaki meslektaşları söyleyenleri tamamlamak, en ufak bir çaba bunu yapardı:

ClassRuby sınıfının Maymun Yaması :

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

ve modelsataları içeren yöntem şu şekilde:

Yöntem Module.constants(yüzeysel olarak) symbolssabitler yerine bir koleksiyon döndürür , bu nedenle yöntem Array#selectaşağıdaki maymun yaması gibi ikame edilebilir 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

Maymun yaması 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

Ve son olarak, modeller yöntemi

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

Bu, projenizdeki tüm model sınıflarını size verecektir.


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

Bu cevapların birçoğunu Rails 4'te başarısızlıkla denedim (wow tanrı sakes için bir veya iki şey değiştirdi) Kendi eklememe karar verdim. ActiveRecord :: Base.connection adı verilen ve tablo adlarını çekenler çalıştı ama istediğim sonucu alamadım çünkü istemediğim bazı modelleri (app / models / içindeki bir klasörde) gizledim delete:

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

Bunu bir başlatıcıya koydum ve her yerden çağırabilirim. Gereksiz fare kullanımını önler.


0

bunu kontrol edebilir

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

0

Tüm modellerin uygulama / modellerde olduğunu ve sunucunuzda grep & awk bulunduğunu varsayarsak (vakaların çoğu),

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

Daha hızlı Rails.application.eager_load!veya her dosya ile döngü Dir.

DÜZENLE:

Bu yöntemin dezavantajı, dolaylı olarak ActiveRecord'dan (örn. FictionalBook < Book) Miras alınan modelleri kaçırmasıdır . En Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)yavaş yol, biraz yavaş olmasına rağmen.


0

Birisi faydalı bulursa bu örneği buraya atıyorum. Çözüm bu cevaba dayanmaktadır https://stackoverflow.com/a/10712838/473040 .

Diyelim ki public_uiddış dünyaya birincil kimlik olarak kullanılan bir sütununuz var (bunu neden yapmak isteyeceğinizin nedenlerini burada bulabilirsiniz )

Şimdi bu alanı mevcut Modeller grubuyla tanıttığınızı ve şimdi henüz ayarlanmamış tüm kayıtları yeniden oluşturmak istediğinizi varsayalım. Bunu böyle yapabilirsin

# 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

şimdi koşabilirsin rake di:public_uids:generate

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.