มีวิธีที่คุณจะได้รับชุดโมเดลทั้งหมดในแอพ 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
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!
ในกรณีที่ทุกคนสะดุดกับเรื่องนี้ฉันมีวิธีแก้ไขปัญหาอื่นโดยไม่พึ่งพาการอ่านหรือขยายชั้นเรียน ...
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
เพื่อที่จะได้รับรายชื่อของทุกรุ่นใน app ของคุณที่คุณทำ:
ApplicationRecord.descendants.collect { |type| type.name }
หรือสั้นกว่า:
ApplicationRecord.descendants.collect(&:name)
หากคุณอยู่ในโหมด dev คุณจะต้องกระตือรือร้นที่จะโหลดโมเดลก่อน:
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
ด้วยRails 6ทำให้Zetiwerkกลายเป็นตัวโหลดโค้ดเริ่มต้น
สำหรับการโหลดที่กระตือรือร้นลอง:
Zeitwerk::Loader.eager_load_all
แล้วก็
ApplicationRecord.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
เพื่อหลีกเลี่ยงการโหลด Rails ล่วงหน้าทั้งหมดคุณสามารถทำได้ดังนี้:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency (f) เหมือนกันกับที่ Rails.application.eager_load!
ใช้ สิ่งนี้ควรหลีกเลี่ยงข้อผิดพลาดไฟล์ที่จำเป็นแล้ว
จากนั้นคุณสามารถใช้โซลูชันทุกชนิดเพื่อระบุรุ่นของ AR เช่น ActiveRecord::Base.descendants
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
นี่คือวิธีการแก้ปัญหาที่ได้รับการตรวจสอบด้วยแอพ 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
จะมีพฤติกรรมเหมือนกับโมเดล แต่จะไม่เชื่อมโยงกับตาราง
ดังนั้นการเสริมสิ่งที่กล่าวว่าเพื่อนร่วมงานข้างต้นความพยายามเพียงเล็กน้อยจะทำเช่นนี้:
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
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
สิ่งนี้จะให้คลาสโมเดลทั้งหมดที่คุณมีในโปรเจ็กต์ของคุณ
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
ฉันใส่มันไว้ใน initializer และสามารถโทรจากที่ใดก็ได้ ป้องกันการใช้เมาส์โดยไม่จำเป็น
สามารถตรวจสอบสิ่งนี้ได้
@models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
สมมติว่าทุกรุ่นอยู่ในแอพ / รุ่นและคุณมี 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
ที่ใช้เป็น ID หลักสำหรับโลกภายนอก (คุณสามารถค้นหาสาเหตุที่คุณต้องการทำที่นี่ )
ตอนนี้สมมติว่าคุณได้เปิดใช้ฟิลด์นี้กับกลุ่มของโมเดลที่มีอยู่และตอนนี้คุณต้องการสร้างระเบียนทั้งหมดที่ยังไม่ได้ตั้งค่าใหม่ คุณสามารถทำสิ่งนี้ได้
# 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