Rails 앱에서 모든 모델을 모을 수있는 방법이 있습니까?


201

Rails 앱에서 모든 모델을 수집 할 수있는 방법이 있습니까?

기본적으로 다음과 같은 작업을 수행 할 수 있습니까?-

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

1
당신은에 의해 답을 참조 레일 엔진 / railties의 모델을 포함하여 모든 모델을 수집해야하는 경우 @jaime
안드레이

레일 5.1에서는 작동하지 않습니다
aks

답변:


98

편집 : 의견과 다른 답변을보십시오. 이것보다 더 똑똑한 답변이 있습니다! 또는 이것을 커뮤니티 위키로 향상 시키십시오.

모델은 마스터 객체에 등록되지 않으므로 Rails에는 모델 목록이 없습니다.

그러나 여전히 응용 프로그램의 models 디렉토리 내용을 볼 수 있습니다 ...

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
  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
참고로, 나는 재미를 위해 두 가지 방법을 모두 사용했습니다. 디렉토리를 검색하는 것은 클래스를 검색하는 것보다 훨씬 빠릅니다. 아마 분명했지만, 지금 당신은 알고 있습니다 :)
Edward Anderson

9
또한 상수 메서드를 통해 모델을 검색하면 요청시 모델 만로드되므로 앱이 시작된 이후 참조되지 않은 항목은 포함되지 않습니다.
Edward Anderson

4
'Kernel.const_get constant_name'을 'eval constant_name'보다 선호합니다.
Jeremy Weathers

3
RAILS_ROOTRails 3에서는 더 이상 사용할 수 없습니다. 대신Dir.glob(Rails.root.join('app/models/*'))
fanaugen

1
실제로 모델은 ActiveRecord::Base이제 자손으로 등록 되므로 모든 모델을 열망하는 경우 모델을 쉽게 반복 할 수 있습니다 (아래 답변 참조).
sj26

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

2
sj26의 의미가 있지만 약간의 실수가있을 수 있습니다. 개발 환경에서 알고있는 한 cache_classes가 꺼져있는 경우 (false) 모든 모델에 액세스하기 위해 응용 프로그램을 수동으로 열망 해야하는 이유입니다. 여기에 설명
masciugo

3
@ Ajedi32 다시, 완전한 답변이 아닙니다. 모델 만로드를 열망하려면 다음을 시도하십시오.Rails.application.paths["app/models"].eager_load!
sj26

119

누군가이 문제에 걸려 넘어 질 경우를 대비하여 dir 읽기 또는 Class 클래스 확장에 의존하지 않는 다른 솔루션이 있습니다 ...

ActiveRecord::Base.send :subclasses

클래스 배열을 반환합니다. 그럼 당신은 할 수 있습니다

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

8
왜 사용하지 말고 ActiveRecord::Base.subclasses사용해야 send합니까? 또한 예를 들어 모델이 표시되기 전에 모델을 "터치"해야하는 것처럼 보입니다 c = Category.new. 그렇지 않으면 그렇지 않습니다.
nonopolarity

52
레일 (3)에서는,이 변경되었습니다ActiveRecord::Base.descendants
토비아스 코헨

3
: subclasses 멤버가 보호되어 있으므로 "보내기"를 사용해야합니다.
Kevin Rood

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 unknown 메소드 또는 변수 오류없이 오브젝트 이름에서 메소드를 호출하려면 다음을 사용하십시오.

model.classify.constantize.attribute_names

8
일부 테이블에는 항상 연결된 모델이 없기 때문에 모델뿐만 아니라 모든 테이블을 가져올 수 있습니다.
courtsimas

이 답변은 테이블 이름을 모델의 복수 이름이 아닌 다른 이름으로 구성하는 것이 가능하고 레거시 설정에서 일반적이므로 잘못된 것으로 간주해야합니다. 이 답변 은 설정이 기본 구성에서 벗어난 경우에도 정답을 제공합니다.
lorefnon

경우에 따라이 방법이 더 효과적 ActiveRecord::Base.send :subclasses입니다. 테이블 이름을 찾는 것이 좋습니다. lorefnon이 언급했듯이 모델 이름을 자동으로 생성하는 것은 문제가 될 수 있습니다.
Tilo

.capitalize.singularize.camelize로 교체 할 수 있습니다 .classify.
Maxim

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 엔진 모델을 포함한 모든 모델을 얻을 수있는 유일한 방법입니다. 팁 고마워!
Andrei

2
몇 가지 유용한 방법 : ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}일부 모델이 활성화되지 않았을 수 있으므로 복구해야합니다.
Andrei

2
안드레이 @ 적응하는 것은 약간의 : 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

충분히 운임, 업데이트
Nimir

저는 Rails 6.0.2와 eager_load에 있습니다! 자손 메서드를 만들어 빈 배열 이외의 것을 반환하지 않았습니다.
jgomo3

23

테이블이없는 모델이 없다면 @ hnovick의 솔루션이 멋진 솔루션이라고 생각합니다. 이 솔루션은 개발 모드에서도 작동합니다.

내 접근 방식은 미묘하지만 다릅니다.

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

classify는 문자열의 클래스 이름을 적절하게 제공 합니다. safe_constantize는 예외를 발생시키지 않고 안전하게 클래스로 바꿀 수 있도록합니다. 모델이 아닌 데이터베이스 테이블이있는 경우에 필요합니다. 열거 형의 0이 제거되도록 압축합니다.


3
멋진 @Aditya Sanghi입니다. 에 대해 몰랐습니다 safe_constantize.
lightyrs

레일 2.3.x의 경우 : ActiveRecord :: Base.connection.tables.map {| x | x.classify.constantize rescue nil} .compact
iheggie

@iheggie 일반적으로 기존 게시물로 편집하는 것보다 별도의 답변으로 게시하는 것이 좋습니다.
Pokechu22

덕분에, 나는 당신이 나를 #adiya에 가장 적합한 답을 발견
마술

21

클래스 이름 만 원하는 경우 :

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

Rails 콘솔에서 실행하면됩니다. 행운을 빕니다!

편집 : @ sj26이 맞습니다. 자손을 호출하기 전에 이것을 먼저 실행해야합니다.

Rails.application.eager_load!

내가 원하는 것 감사!
sunsations

map와 전화 puts? 난 포인트가 없어ActiveRecord::Base.descendants.map(&:model_name)
Nuno Costa

그렇게 할 수는 있지만, 훨씬 읽기 쉬운 형식으로 한 줄이 아닌 단일 배열에 있습니다.
Jordan Michael Rushing

17

이것은 나를 위해 작동하는 것 같습니다 :

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

고마워요 bhousel. 필자는 원래이 스타일의 접근 방식을 사용했지만 파일 이름을 "모델화"할 필요가 없음을 의미하여 Vincent가 위에 게시 한 솔루션을 사용했습니다. 다시)).
mr_urf

하위 디렉토리 포함 :...'/app/models/**/*.rb'
artemave

Object.subclasses_of 는 v2.3.8 이후 더 이상 사용되지 않습니다.
David J.

11

한 줄에 : 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

2
@wbharding 그것은 꽤 좋지만, rspec 모델 테스트의 이름을 일정하게 만들려고 할 때 오류가 발생합니다. ;-)
Ajedi32

좋은 솔루션을 @wbharding 그러나 당신이 네임 스페이스 한 모델을 나누기
마커스 만수르

10

ActiveRecord::Base.connection.tables


또한 <table_name> .column_names를 사용하여 테이블의 모든 열을 나열합니다. 따라서 사용자 테이블의 경우 User.column_names
Mark Locklear를

일부 테이블에는 항상 연결된 모델이 없기 때문에 모델뿐만 아니라 모든 테이블을 가져올 수 있습니다.
courtsimas

7

한 줄로 :

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

2
그것은 모든 모델을 보여주지는 않습니다. 이유가 확실하지 않습니다. 사실 몇 가지 짧습니다.
courtsimas

1
나를 위해 일했다. '그 모든 것에 대답하기에는 조금 늦었습니다. 시간을 줘.
boulder_ruby

2
Rails.application.eager_load!개발 모드에서 실행하기 전에 필요할 수 있습니다.
denis.peplin

7

나는 아직 언급 할 수는 없지만 sj26 답변 이 최고 답변이어야 한다고 생각 합니다. 힌트 만 :

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

6

함께 레일 6 , Zetiwerk는 기본 코드 로더가되었다.

열심히로드하려면 다음을 시도하십시오.

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에 대한 upvolt.application.eager_load! idea
equivalent8

3

모든 레일을 사전로드하지 않으려면 다음을 수행하십시오.

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

require_dependency (f)는 사용하는 것과 같습니다 Rails.application.eager_load!. 이미 필요한 파일 오류를 피해야합니다.

그런 다음 모든 종류의 솔루션을 사용하여 다음과 같은 AR 모델을 나열 할 수 있습니다. ActiveRecord::Base.descendants


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

콘솔에서 TypeError : Symbol을 String으로 암시 적으로 변환하지 않습니다.
snowangel

1

다음은 복잡한 Rails 앱 (하나의 파워 스퀘어)으로 검증 된 솔루션입니다.

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 모델과 같은 동작을합니다은 그냥 테이블에 연결됩니다하지 않습니다.

따라서 위의 동료가 말한 것을 보완하기 위해 약간의 노력으로이를 수행 할 수 있습니다.

Class루비 클래스 의 원숭이 패치 :

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

그리고 마지막으로 모델 방법

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 에서 많은 답변을 실패했습니다 (하나님을 위해 하나 또는 두 가지를 바꿨습니다). 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

모든 모델이 앱 / 모델에 있고 서버에 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.