2009-02-05 7 views
171

Есть ли способ получить коллекцию всех моделей в вашем приложении Rails?Есть ли способ получить коллекцию всех моделей в вашем приложении Rails?

В принципе, я могу сделать, подобного: -

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

Если вам необходимо собрать все модели, включая модели Rails двигатели/Railties, смотрите ответ на [@jaime] (http://stackoverflow.com/questions/516579/is-there-a-way-to-get-a-collection-of-all-the-models-in-your-rails-app/4598129 # 4598129) – Andrei

+0

Не работает на рельсах 5.1 – aks

ответ

89

EDIT: Посмотрите на комментарии и другие ответы. Есть более умные ответы, чем этот! Или попробуйте улучшить это как сообщество wiki.

Модели не регистрируются на главном объекте, поэтому нет, у Rails нет списка моделей.

Но вы могли бы еще посмотреть в содержании каталога моделей вашего приложения ...

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

EDIT: Еще одна идея (дикий) будет использовать Ruby, отражение искать для каждого класса, который простирается ActiveRecord ::База. Не знаю, как вы можете перечислить все классы, хотя ...

EDIT: Просто для удовольствия, я нашел способ перечислить все классы

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

EDIT: Наконец удалось перечисляя все модели, не глядя в каталогах

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 
+0

Достаточно смешно , Я пошел по-другому! Я начал смотреть на размышления, но не мог понять, как получить список классов! Сразу после того, как я опубликовал сообщение, у меня был мгновенный момент, и я пошел посмотреть Дир. Спасибо за ответ. Очень признателен. –

+0

Вау! Кудос заработал, а потом кто-то! Спасибо за это, я пошел с предложением Dir и в конечном итоге пришлось сканировать, стирать и использовать заглавные буквы! результирующее имя файла. Однако я думаю, что ваше решение намного элегантнее, поэтому я буду использовать его вместо этого. Если бы я мог проголосовать за тебя снова, я бы! :) –

+5

FYI, я приурочил оба метода просто для удовольствия. Поиск каталогов на порядок выше, чем поиск по классам. Вероятно, это было очевидно, но теперь вы знаете :) –

17

Это, кажется, работает для меня:

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

Rails загружает только модели, когда они используются, поэтому Dir.glob линия «требует msgstr "все файлы в каталоге моделей.

После того, как у вас есть модели в массиве, вы можете делать то, что вы думаете (например, вид кода):

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

Спасибо bhousel. Я изначально пошел с этим подходом, но в итоге использовал решение, которое Vincent разместил выше, поскольку это означало, что мне не нужно было «моделировать» имя файла (т. Е. Вырезать любое _, заглавное! Каждое слово и затем присоединиться их снова). –

+0

с подкаталогами: '... '/ app/models/**/*. Rb'' – artemave

+0

[Object.subclasses_of] (http://apidock.com/rails/Object/subclasses_of) устарел после v2.3.8. –

111

Только в случае, если кто-нибудь натыкается на этом, у меня есть другое решение, не полагаясь на показания дирижера или расширяя класс класса ...

ActiveRecord::Base.send :subclasses 

Это вернет массив классов.Таким образом, вы можете сделать

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

Почему вы не используете 'ActiveRecord :: Base.subclasses', но должны использовать' send'? Кроме того, кажется, что вам нужно «коснуться» модели до ее появления, например 'c = Category.new', и она появится. В противном случае это не произойдет. –

+0

Я не помню, почему я использовал отправить больше, но, вероятно, была причина. Возможно, на версии rails, которую я использовал в то время, подклассы были частными. Вы были правы в отношении «необходимости прикоснуться». – kikito

+52

В Rails 3 это было изменено на 'ActiveRecord :: Base.descendants' –

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

+0

@wbharding приятное решение, но оно ломается, когда у вас есть модели с именами –

28

Я искал способы, чтобы сделать это, и в конечном итоге выбрали этот путь:

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' –

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

вернет

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

Дополнительная информация Если вы хотите вызвать метод с именем объекта без модели: строка неизвестный метод или переменная ошибки используют этот

model.classify.constantize.attribute_names 
+7

Это даст вам все таблицы, но не только модели, так как некоторые таблицы не всегда имеют связанные модели. – courtsimas

+1

Это замечательный момент; Благодарю. – lightyrs

+0

Этот ответ должен считаться некорректным, поскольку это возможно (и распространено в устаревших установках), чтобы настроить имя таблицы как нечто отличное от плюрализованного имени модели. [Этот ответ] (http://stackoverflow.com/a/36277614/476712) дает правильный ответ, даже если настройка отличается от конфигурации по умолчанию. – lorefnon

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

throws TypeError: неявное преобразование символа в строку в консоли. – snowangel

6

ActiveRecord::Base.connection.tables

+0

Также хороший комментарий .column_names для отображения всех столбцов в таблице. Таким образом, для вашей таблицы пользователей вы должны выполнить User.column_names – Lumbee

+0

Это приведет к вам все таблицы, хотя и не только для моделей, поскольку некоторые таблицы не всегда имеют связанные модели. – courtsimas

20

Я думаю, @ hnovick-х решение является классным, если у вас нет моделей без таблиц. Это решение будет работать в режиме разработки, а

Мой подход немного отличается, хотя -

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

Классифицировать хорошо должен дать вам имя класса из строки правильно. safe_constantize гарантирует, что вы можете безопасно превратить его в класс без исключения исключения. Это необходимо, если у вас есть таблицы базы данных, которые не являются моделями. компактный, так что любые нули в перечислении удаляются.

+2

Это потрясающе @Aditya Sanghi. Я не знал о 'safe_constantize'. – lightyrs

+0

Для рельсов 2.3.x, используйте: ActiveRecord :: Base.connection.tables.map {| x | x.classify.constantize rescue nil} .compact – iheggie

+0

@iheggie Обычно лучше публиковать это как отдельный ответ, чем редактируя его в существующую почту. – Pokechu22

327

Весь ответ на Rails 3, 4 и 5:

Если cache_classes выключен (по умолчанию он выключен в разработке, но в производстве):

Rails.application.eager_load! 

Тогда:

ActiveRecord::Base.descendants 

Это гарантирует, что все модели в вашем приложении, независимо от того, где они находятся, загружаются, а также любые драгоценные камни, которые вы используете, которые предоставляют модели, также загружаются.

Это также должно работать на классы, которые наследуют от ActiveRecord::Base, как ApplicationRecord в Rails 5, и вернуть только то, что поддерево потомков:

ApplicationRecord.descendants 

Если вы хотите узнать больше о том, как это сделано, проверьте ActiveSupport::DescendantsTracker.

+29

Удивительный! Это должен быть принятый ответ. Для кого-либо, использующего это в граблированной задаче: заставьте свою задачу зависеть от ': environment' для' eager_load! '. –

+1

Или, как немного более быстрая альтернатива 'Rails.application.eager_load!', Вы можете просто загрузить модели: 'Dir.glob (Rails.root.join ('app/models/*')). Each do | x | require x end' – Ajedi32

+0

Я согласен с Джо Лиссом: это должен быть главный ответ! 2 линии вместо 14 в 7 раз более удивительны. –

7

только в одной строке:

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

Это не показывает все модели для меня. Не знаю, почему. На самом деле это пара коротких слов. – courtsimas

+1

работал для меня. «немного опоздать, чтобы ответить на все. это займет время. –

+2

Вероятно, перед выполнением в режиме разработки, вероятно, требуется «Rails.application.eager_load!». –

1

Я хотел бы прокомментировать ответ sj26, который является тот, который я предпочитаю, как я работаю в среде разработки, но я не могу из-за моей молодой репутации , Я понял, что он имел в виду, но, возможно, есть небольшая ошибка: насколько я знаю в среде разработки cache_classes отключен (false), поэтому вам нужно вручную загружать приложение для доступа ко всем моделям.

+0

Чтобы оставить комментарий, добавьте комментарий, а не ответ. – Archonic

+1

Вы правы, но, как объяснялось, я не хотел комментировать. Теперь я и так и сделал. спасибо – masciugo

0

может проверить этот

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

Вот решение, которое было проверено с комплексным приложением 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 и т.д.

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

Можете ли вы дать некоторое объяснение кода, а также ..? – NREZ

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}"}} 
5

Я еще не могу прокомментировать, но я думаю, что sj26 answer должен быть лучшим ответом. Подсказка:

Rails.application.eager_load! unless Rails.configuration.cache_classes 
ActiveRecord::Base.descendants 
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 
0

Я пробовал многие из этих ответов безуспешно в Rails 4 (ничего себе они изменили одну или две вещи для бога пользы), я решил добавить мой собственный. Те, которые вызвали ActiveRecord :: Base.connection и вытащили имена таблиц, работали, но не получили результат, который я хотел, потому что я спрятал некоторые модели (в папке внутри приложения/models /), которую я не хотел delete:

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

Я положил это в инициализатор и могу назвать его из любого места. Предотвращает ненужное использование мыши.

18

Если вы хотите только имена классов:

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

Просто запустить его в Rails консоли, ничего больше. Удачи!

EDIT: @ sj26 правильно, вам нужно запустить это первым, прежде чем вы можете называть потомков:

Rails.application.eager_load! 
+0

Только то, что я хотел. Спасибо! – sunsations

+0

вызов 'map' с' puts'? Я не понимаю, должен быть 'ActiveRecord :: Base.descendants.map (&: model_name)' –

+0

Вы можете сделать это таким образом, но они будут в одном массиве, а не в строках, в гораздо более удобном для чтения формате. –

2

Это работает для 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 
+0

upvolt для этого Rails.application.eager_load! Идея – equivalent8

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.

EDIT:

Недостатком этого метода является то, что он пропускает модели, которые косвенно наследуются от ActiveRecord (например FictionalBook < Book). Самый верный способ - Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name), хотя он медленный.

12

Для Rails5 моделей are now subclasses из ApplicationRecord так, чтобы получить список всех моделей в вашем приложении вы делаете:

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

Или короче:

ApplicationRecord.descendants.collect(&:name) 

Если вы находитесь в режиме разработчика, вы необходимо будет поддерживать модели нагрузки до:

Rails.application.eager_load! 
+0

Я полагаю, что это потребует, чтобы классы уже были загружены и дали бы неполные результаты в среде разработки с включенной автозагрузкой. Я не буду понижать, но, возможно, это следует упомянуть в ответе. – lorefnon

+0

достаточно, обновление – Nimir

0

Я просто бросаю этот пример здесь, если кто-то считает его полезным. Решение основано на этом ответе https://stackoverflow.com/a/10712838/473040.

Пусть у вас есть столбец public_uid, который используется в качестве основного идентификатора внешнего мира (вы можете findjreasons почему вы хотели бы сделать это 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

1

Rails реализует метод descendants, но модели не обязательно когда-нибудь наследует от ActiveRecord::Base, например, класс, который включает в себя модуль ActiveModel::Model будет иметь то же поведение, что и модель, просто не связано с таблицей.

Так дополняя то, что говорит, что выше его коллеги, малейшее усилие будет сделать это:

обезьяна патч класса Class рубина:

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

и метод models, в том числе предков, как это:

Метод Module.constants возвращает (по существу) коллекцию symbols, вместо констант, поэтому метод 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 
2

Чтобы избежать предварительной нагрузки все Rails, вы можете сделать это:

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

require_dependency (е) то же самое, что Rails.application.eager_load! использует. Это должно избежать уже требуемых ошибок файла.

Затем вы можете использовать все виды решений в список моделей AR, как ActiveRecord::Base.descendants

1

Да, есть много способов, вы можете найти все имена моделей, но то, что я сделал в моем драгоценном камне model_info, оно даст вам все модели даже включены в драгоценные камни.

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