Чи є спосіб отримати колекцію всіх моделей у додатку Rails?
В основному, чи можу я зробити так, як:
Models.each do |model|
puts model.class.name
end
Чи є спосіб отримати колекцію всіх моделей у додатку Rails?
В основному, чи можу я зробити так, як:
Models.each do |model|
puts model.class.name
end
Відповіді:
EDIT: Подивіться на коментарі та інші відповіді. Є розумніші відповіді, ніж ця! Або спробуйте покращити цю як вікі спільноти.
Моделі не реєструються в основному об'єкті, тому ні, у Rails немає списку моделей.
Але ви все ще можете шукати вміст каталогу моделей вашої програми ...
Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end
EDIT: Ще однією (дикою) ідеєю було б використовувати відображення Ruby для пошуку всіх класів, які розширюють 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
RAILS_ROOT
більше не доступний у Rails 3. Натомість використовуйтеDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
нині, тому якщо ви прагнете завантажити всі моделі, то ви можете легко їх повторити - дивіться мою відповідь нижче.
Вся відповідь на рейки 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
- хороша ідея пошуку назв таблиць. Автоматичне генерування імен моделей може бути проблематичним, як згадується lorefnon.
.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, нічого більше. Удачі!
EDIT: @ 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
Так, є багато способів знайти всі назви моделей, але те, що я зробив у своєму самоцвіті 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
Це працює для 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
Щоб уникнути попереднього завантаження всіх рейок, ви можете зробити це:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
need_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 і витягували назви таблиць, працювали, але не отримали результату, який я хотів, тому що я заховав деякі моделі (у папці всередині додатка / моделей /), які не хотів видалити:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
Я поміщаю це в ініціалізатор і можу викликати його з будь-якого місця. Запобігає зайвому використанню миші.
Якщо припустити, що всі моделі є в додатках / моделях, і у вас на сервері є греп & 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