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

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

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

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

Ответ 1

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

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

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

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

EDIT: Еще одна (дикая) идея - использовать Ruby reflection для поиска всех классов, расширяющих ActiveRecord:: Base. Не знаю, как вы можете перечислить все классы, хотя...

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

Ответ 2

Весь ответ для Rails 3, 4 и 5:

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

Rails.application.eager_load!

Тогда:

ActiveRecord::Base.descendants

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

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

ApplicationRecord.descendants

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

Ответ 3

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

ActiveRecord::Base.send :subclasses

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

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

Ответ 4

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

вернет

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

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

model.classify.constantize.attribute_names

Ответ 6

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

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

Или короче:

ApplicationRecord.descendants.collect(&:name)

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

Rails.application.eager_load!

Ответ 7

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

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

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

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

Ответ 8

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

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

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

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

Rails.application.eager_load!

Ответ 9

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

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

Ответ 10

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

Ответ 11

ActiveRecord::Base.connection.tables

Ответ 12

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

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

Ответ 13

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

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

Ответ 14

Да, есть много способов найти все имена моделей, но то, что я сделал в своем драгоценном камне 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

Ответ 15

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

Ответ 16

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

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

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

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

Ответ 17

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

Ответ 18

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

Ответ 19

Здесь решение, которое было проверено с помощью сложного 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

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

Ответ 20

Просто наткнулся на это, так как мне нужно распечатать все модели с их атрибутами (построено на комментарии @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}"}}

Ответ 21

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

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

Ответ 22

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

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

Monkey Patch класса Class Ruby:

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

Ответ 23

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

Это даст вам все классы моделей, которые есть в вашем проекте.

Ответ 24

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

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

Ответ 25

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

Ответ 26

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

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

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

Ответ 27

Предполагая, что все модели находятся в приложении/моделях, и у вас есть 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), хотя он медленный.

Ответ 28

Я просто бросаю этот пример здесь, если кто-то считает его полезным. Решение основано на этом ответе fooobar.com/questions/36993/....

Скажем, у вас есть столбец 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

Ответ 29

With Rails 6, Zetiwerk стал загрузчиком кода по умолчанию.

Для быстрой загрузки попробуйте:

Zeitwerk::Loader.eager_load_all

Тогда

ApplicationRecord.descendants