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

201

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

В основном, я могу сделать, как: -

Models.each do |model|
  puts model.class.name
end
mr_urf
источник
1
Если вам нужно собрать все модели, включая модели двигателей / рельсов Rails, см. Ответ @jaime
Andrei
Не работает на рельсах 5.1
aks

Ответы:

98

РЕДАКТИРОВАТЬ: Посмотрите на комментарии и другие ответы. Есть более умные ответы, чем этот! Или попробуйте улучшить это как сообщество вики.

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

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

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 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
Vincent Robert
источник
6
К вашему сведению, я рассчитал оба метода просто для удовольствия. Поиск по каталогам на порядок быстрее, чем поиск по классам. Это было, вероятно, очевидно, но теперь вы знаете :)
Эдвард Андерсон
9
Кроме того, важно отметить, что поиск моделей с помощью методов констант не будет включать ничего, на что не было ссылок с момента запуска приложения, поскольку оно загружает модели только по требованию.
Эдвард Андерсон
4
Я предпочитаю 'Kernel.const_get constant_name', чем 'eval constant_name'.
Джереми Уэзерс
3
RAILS_ROOTбольше не доступен в Rails 3. Вместо этого используйтеDir.glob(Rails.root.join('app/models/*'))
fanaugen
1
На самом деле, модели сейчас регистрируются как потомки ActiveRecord::Base, поэтому, если вы хотите загрузить все модели, вы можете легко их повторить - см. Мой ответ ниже.
sj26
394

Полный ответ для Rails 3, 4 и 5:

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

Rails.application.eager_load!

Затем:

ActiveRecord::Base.descendants

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

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

ApplicationRecord.descendants

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

sj26
источник
33
Потрясающие! Это должен быть принятый ответ. Для любого, кто использует это в граблях: сделайте так, чтобы ваша задача зависела от :environmentтого, eager_load!как она работает
Джо Лисс
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

На всякий случай, если кто-то наткнется на это, у меня есть другое решение, не полагаясь на чтение директории или расширение класса Class ...

ActiveRecord::Base.send :subclasses

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

ActiveRecord::Base.send(:subclasses).map(&:name)
kikito
источник
8
почему вы не используете, ActiveRecord::Base.subclassesно должны использовать send? Кроме того, кажется, что вам нужно «дотронуться» до того, как модель появится, например, c = Category.newи она появится. В противном случае это не так.
неполярность
52
В Rails 3 это было изменено наActiveRecord::Base.descendants
Тобиас Коэн
3
Вы должны использовать «send», потому что член: subclasses защищен.
Кевин Руд
11
Спасибо за совет Rails 3. Для всех, кто придет, вам все равно нужно «потрогать» модели, прежде чем ActiveRecord::Base.descendantsперечислять их.
МСЧ
3
Технически в Rails 3 у вас есть подклассы и потомки, они означают разные вещи.
sj26
67
ActiveRecord::Base.connection.tables.map do |model|
  model.capitalize.singularize.camelize
end

вернется

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

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

model.classify.constantize.attribute_names
lightyrs
источник
8
Это даст вам все таблицы, а не только модели, поскольку некоторые таблицы не всегда имеют связанные модели.
Courtimas
Этот ответ следует считать неверным, поскольку выполнимо (и часто встречается в устаревших установках) настроить имя таблицы как нечто отличное от множественного имени модели. Этот ответ дает правильный ответ, даже если установка отличается от конфигурации по умолчанию.
Lorefnon
в некоторых случаях это работает лучше, чем ActiveRecord::Base.send :subclassesпоиск имен таблиц - хорошая идея. Автоматическая генерация названий моделей может быть проблематичной, как уже упоминалось ранее.
Тило
.capitalize.singularize.camelizeможно заменить на .classify.
Максим,
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, используемые в приложении. Спасибо за чаевые!
Андрей
2
Несколько полезных методов: ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}некоторые из моделей могут быть не активированы, поэтому вам нужно их спасти.
Андрей
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
достаточно проезд, обновление
Нимир
Я на Rails 6.0.2 и на eager_load! не сделал метод потомков для возврата ничего, кроме пустого массива.
jgomo3
23

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

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

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

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

Адитья Санги
источник
3
Это потрясающе @ Aditya Sanghi. Я не знал о safe_constantize.
зажигалки
Для рельсов 2.3.x используйте: ActiveRecord :: Base.connection.tables.map {| x | x.classify.constantize rescue nil} .compact
iheggie
@iheggie Обычно лучше опубликовать это как отдельный ответ, чем редактировать его в существующем сообщении.
Pokechu22
спасибо, я нашел ваш ответ лучше всего подходит для меня #adiya
illusionist
21

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

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

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

РЕДАКТИРОВАТЬ: @ sj26 правильно, вам нужно сначала запустить это, прежде чем вы можете вызвать потомков:

Rails.application.eager_load!
Джордан Майкл Рашинг
источник
Как раз то, что я хотел. Спасибо!
Солнца
звонить mapс puts? Я не понимаю, точка должна бытьActiveRecord::Base.descendants.map(&:model_name)
Нуно Коста
Вы можете сделать это таким образом, но они будут в одном массиве, а не построчно, в гораздо более удобном для чтения формате.
Джордан Майкл Рашинг
17

Кажется, это работает для меня:

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

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

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

<% @models.each do |v| %>
  <li><%= h v.to_s %></li>
<% end %>
bhousel
источник
Спасибо, bhousel. Первоначально я придерживался этого стиля подхода, но в итоге использовал решение, которое Винсент опубликовал выше, поскольку это означало, что мне не нужно было также «моделировать» имя файла (т. Е. Вырезать любое _, использовать заглавные буквы! Каждое слово и затем присоединяться их снова).
mr_urf
с подкаталогами:...'/app/models/**/*.rb'
artemave
Object.subclasses_of устарела после v2.3.8.
Дэвид Дж
11

На одной строке: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }

VJT
источник
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
Это даст вам все таблицы, а не только модели, поскольку некоторые таблицы не всегда имеют связанные модели.
courtimas
7

Всего в одной строке:

 ActiveRecord::Base.subclasses.map(&:name)
Адриан
источник
2
Это не показывает все модели для меня. Не уверен почему. Это пара коротких, на самом деле.
courtimas
1
работал на меня. просто немного поздно, чтобы ответить, вот и все. это займет время.
boulder_ruby
2
Это, вероятно, нужно Rails.application.eager_load!перед выполнением в режиме разработки.
denis.peplin
7

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

Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
panteo
источник
6

С Rails 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
ryan0
источник
upvolt для этого Rails.application.eager_load! идея
эквивалент8
3

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

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}"}}
gouravtiwari21
источник
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
Kevin
источник
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
rplaurindo
источник
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

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

boulder_ruby
источник
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)хоть и медленный.

konyak
источник
0

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

Допустим, у вас есть столбец, public_uidкоторый используется в качестве основного идентификатора для внешнего мира (вы можете найти причины, почему вы хотели бы сделать это здесь )

Теперь допустим, что вы ввели это поле для множества существующих моделей, и теперь вы хотите восстановить все записи, которые еще не установлены. Вы можете сделать это так

# 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

equivalent8
источник