Есть ли способ, которым вы можете получить коллекцию всех моделей в вашем приложении Rails?
В основном, я могу сделать, как: -
Models.each do |model|
puts model.class.name
end
Есть ли способ, которым вы можете получить коллекцию всех моделей в вашем приложении Rails?
В основном, я могу сделать, как: -
Models.each do |model|
puts model.class.name
end
Ответы:
РЕДАКТИРОВАТЬ: Посмотрите на комментарии и другие ответы. Есть более умные ответы, чем этот! Или попробуйте улучшить это как сообщество вики.
Модели не регистрируются в главном объекте, поэтому в 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
RAILS_ROOT
больше не доступен в Rails 3. Вместо этого используйтеDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
, поэтому, если вы хотите загрузить все модели, вы можете легко их повторить - см. Мой ответ ниже.
Полный ответ для Rails 3, 4 и 5:
Если cache_classes
выключен (по умолчанию он выключен в разработке, но включен в работе):
Rails.application.eager_load!
Затем:
ActiveRecord::Base.descendants
Это гарантирует, что все модели в вашем приложении, независимо от того, где они находятся, загружены, и все используемые вами драгоценные камни, которые предоставляют модели, также загружены.
Это также должно работать с классами, которые наследуются ActiveRecord::Base
, как ApplicationRecord
в Rails 5, и возвращать только это поддерево потомков:
ApplicationRecord.descendants
Если вы хотите узнать больше о том, как это сделать, проверьте ActiveSupport :: DescendantsTracker .
:environment
того, eager_load!
как она работает
Rails.application.eager_load!
, вы можете просто загрузить модели:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
каталоги. Стремительная загрузка всего приложения - более полный ответ и гарантирует, что для определения моделей абсолютно не осталось места.
Rails.application.paths["app/models"].eager_load!
На всякий случай, если кто-то наткнется на это, у меня есть другое решение, не полагаясь на чтение директории или расширение класса Class ...
ActiveRecord::Base.send :subclasses
Это вернет массив классов. Так что вы можете сделать
ActiveRecord::Base.send(:subclasses).map(&:name)
ActiveRecord::Base.subclasses
но должны использовать send
? Кроме того, кажется, что вам нужно «дотронуться» до того, как модель появится, например, c = Category.new
и она появится. В противном случае это не так.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
перечислять их.
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
вернется
["Article", "MenuItem", "Post", "ZebraStripePerson"]
Дополнительная информация Если вы хотите вызвать метод для имени объекта без модели: строка неизвестный метод или переменные ошибки используйте это
model.classify.constantize.attribute_names
ActiveRecord::Base.send :subclasses
поиск имен таблиц - хорошая идея. Автоматическая генерация названий моделей может быть проблематичной, как уже упоминалось ранее.
.capitalize.singularize.camelize
можно заменить на .classify
.
Я искал способы сделать это и в итоге выбрал этот путь:
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
ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end}
некоторые из моделей могут быть не активированы, поэтому вам нужно их спасти.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Для Rails5 моделей теперь подклассы из ApplicationRecord
так , чтобы получить список всех моделей в вашем приложении вы делаете:
ApplicationRecord.descendants.collect { |type| type.name }
Или короче:
ApplicationRecord.descendants.collect(&:name)
Если вы находитесь в режиме разработки, вам нужно будет загрузить модели до:
Rails.application.eager_load!
Я думаю, что решение @ hnovick будет классным, если у вас нет моделей без таблиц. Это решение будет работать и в режиме разработки
Мой подход немного отличается, хотя -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
Классифицировать хорошо должно дать вам имя класса из строки должным образом . safe_constantize гарантирует, что вы можете безопасно превратить его в класс без исключения. Это необходимо, если у вас есть таблицы базы данных, которые не являются моделями. компактный, так что все нули в перечислении удаляются.
safe_constantize
.
Если вы хотите только имена классов:
ActiveRecord::Base.descendants.map {|f| puts f}
Просто запустите его в консоли Rails, не более того. Удачи!
РЕДАКТИРОВАТЬ: @ sj26 правильно, вам нужно сначала запустить это, прежде чем вы можете вызвать потомков:
Rails.application.eager_load!
map
с puts
? Я не понимаю, точка должна бытьActiveRecord::Base.descendants.map(&:model_name)
Кажется, это работает для меня:
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 %>
...'/app/models/**/*.rb'
На одной строке: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
ActiveRecord::Base.connection.tables
Всего в одной строке:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
перед выполнением в режиме разработки.
Я пока не могу комментировать, но я думаю, что ответ sj26 должен быть лучшим ответом. Просто подсказка:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
Да, есть много способов найти все названия моделей, но то, что я сделал в моем 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
Это работает для 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, вы можете сделать это:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency (f) - это то же самое, что Rails.application.eager_load!
использует. Это должно избежать уже требуемых ошибок файла.
Затем вы можете использовать все виды решений для перечисления моделей AR, например, ActiveRecord::Base.descendants
Вот решение, которое было проверено со сложным приложением 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 и т. Д.
Только что наткнулся на эту, так как мне нужно напечатать все модели с их атрибутами (основано на комментарии @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}"}}
Это сработало для меня. Отдельное спасибо всем постам выше. Это должно вернуть коллекцию всех ваших моделей.
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
В 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
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
Я безуспешно перепробовал многие из этих ответов в 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
Я помещаю это в инициализатор и могу вызвать его из любого места. Предотвращает ненужное использование мыши.
Предполагая, что все модели находятся в приложении / модели, и у вас есть 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)
хоть и медленный.
Я просто привожу этот пример здесь, если кто-нибудь найдет его полезным. Решение основано на этом ответе 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