Có cách nào để bạn có thể nhận được một bộ sưu tập tất cả các Mô hình trong ứng dụng Rails của mình không?
Về cơ bản, tôi có thể làm như:
Models.each do |model|
puts model.class.name
end
Có cách nào để bạn có thể nhận được một bộ sưu tập tất cả các Mô hình trong ứng dụng Rails của mình không?
Về cơ bản, tôi có thể làm như:
Models.each do |model|
puts model.class.name
end
Câu trả lời:
EDIT: Nhìn vào các ý kiến và câu trả lời khác. Có câu trả lời thông minh hơn câu trả lời này! Hoặc cố gắng cải thiện cái này như wiki cộng đồng.
Các mô hình không tự đăng ký vào một đối tượng chính, vì vậy không, Rails không có danh sách các mô hình.
Nhưng bạn vẫn có thể xem nội dung của thư mục mô hình của ứng dụng của bạn ...
Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end
EDIT: Một ý tưởng (hoang dã) khác là sử dụng phản chiếu Ruby để tìm kiếm mọi lớp mở rộng ActiveRecord :: Base. Không biết làm thế nào bạn có thể liệt kê tất cả các lớp mặc dù ...
EDIT: Để giải trí, tôi đã tìm ra cách liệt kê tất cả các lớp
Module.constants.select { |c| (eval c).is_a? Class }
EDIT: Cuối cùng đã thành công trong việc liệt kê tất cả các mô hình mà không cần nhìn vào các thư mục
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
Nếu bạn cũng muốn xử lý lớp dẫn xuất, thì bạn sẽ cần phải kiểm tra toàn bộ chuỗi siêu lớp. Tôi đã làm điều đó bằng cách thêm một phương thức vào lớp 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
không còn khả dụng trong Rails 3. Thay vào đó, hãy sử dụngDir.glob(Rails.root.join('app/models/*'))
ActiveRecord::Base
bây giờ, vì vậy nếu bạn háo hức tải tất cả các mô hình thì bạn có thể lặp lại chúng một cách dễ dàng. Xem câu trả lời của tôi dưới đây.
Toàn bộ câu trả lời cho Rails 3, 4 và 5 là:
Nếu cache_classes
bị tắt (theo mặc định, nó sẽ bị phát triển, nhưng đang trong quá trình sản xuất):
Rails.application.eager_load!
Sau đó:
ActiveRecord::Base.descendants
Điều này đảm bảo tất cả các mô hình trong ứng dụng của bạn, bất kể chúng ở đâu, được tải và bất kỳ đá quý nào bạn đang sử dụng cung cấp các mô hình cũng được tải.
Điều này cũng sẽ hoạt động trên các lớp kế thừa từ ActiveRecord::Base
, như ApplicationRecord
trong Rails 5 và chỉ trả về cây con của con cháu:
ApplicationRecord.descendants
Nếu bạn muốn biết thêm về cách thực hiện, hãy xem ActiveSupport :: DescendantsTracker .
:environment
cho eager_load!
đến công việc.
Rails.application.eager_load!
, bạn chỉ có thể tải các mô hình:Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
Rails.paths["app/models"].existent
thư mục. Háo hức tải toàn bộ ứng dụng là một câu trả lời đầy đủ hơn và sẽ đảm bảo rằng không còn chỗ nào cho các mô hình được xác định.
Rails.application.paths["app/models"].eager_load!
Chỉ trong trường hợp bất kỳ ai vấp phải vấn đề này, tôi đã có một giải pháp khác, không dựa vào việc đọc thư mục hoặc mở rộng lớp Lớp ...
ActiveRecord::Base.send :subclasses
Điều này sẽ trả về một mảng các lớp. Vì vậy, bạn có thể làm
ActiveRecord::Base.send(:subclasses).map(&:name)
ActiveRecord::Base.subclasses
mà phải sử dụng send
? Ngoài ra, có vẻ như bạn phải "chạm" vào mô hình trước khi nó xuất hiện, ví dụ c = Category.new
và nó sẽ hiển thị. Nếu không, nó sẽ không.
ActiveRecord::Base.descendants
ActiveRecord::Base.descendants
liệt kê chúng.
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
sẽ trở lại
["Article", "MenuItem", "Post", "ZebraStripePerson"]
Thông tin bổ sung Nếu bạn muốn gọi một phương thức trên tên đối tượng mà không có mô hình: phương thức không xác định chuỗi hoặc lỗi biến sử dụng
model.classify.constantize.attribute_names
ActiveRecord::Base.send :subclasses
- tìm kiếm tên bảng là một ý tưởng tốt. Tự động tạo tên mô hình có thể có vấn đề như lorefnon đã đề cập.
.capitalize.singularize.camelize
có thể được thay thế để .classify
.
Tôi đã tìm cách để làm điều này và cuối cùng đã chọn cách này:
in the controller:
@data_tables = ActiveRecord::Base.connection.tables
in the view:
<% @data_tables.each do |dt| %>
<br>
<%= dt %>
<% end %>
<br>
nguồn: 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}
Một số mô hình có thể không được kích hoạt do đó bạn cần phải giải cứu nó.
model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
Đối với Rails5 mô hình hiện nay là lớp con của ApplicationRecord
như vậy để có được danh sách của tất cả các mẫu trong ứng dụng của bạn, bạn cần làm:
ApplicationRecord.descendants.collect { |type| type.name }
Hoặc ngắn hơn:
ApplicationRecord.descendants.collect(&:name)
Nếu bạn đang ở chế độ dev, bạn sẽ cần phải háo hức tải các mô hình trước:
Rails.application.eager_load!
Tôi nghĩ rằng giải pháp của @ hnovick là một giải pháp tuyệt vời nếu bạn không có các mô hình không có bảng. Giải pháp này cũng sẽ hoạt động trong chế độ phát triển.
Cách tiếp cận của tôi rất khác biệt mặc dù -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
phân loại cũng được cho là cung cấp cho bạn tên của lớp từ một chuỗi đúng . safe_constantize đảm bảo rằng bạn có thể biến nó thành một lớp an toàn mà không cần ném ngoại lệ. Điều này là cần thiết trong trường hợp bạn có các bảng cơ sở dữ liệu không phải là mô hình. nhỏ gọn để bất kỳ nils trong liệt kê được loại bỏ.
safe_constantize
.
Nếu bạn chỉ muốn tên lớp:
ActiveRecord::Base.descendants.map {|f| puts f}
Chỉ cần chạy nó trong bảng điều khiển Rails, không có gì hơn. Chúc may mắn!
EDIT: @ sj26 là đúng, bạn cần chạy cái này trước khi bạn có thể gọi con cháu:
Rails.application.eager_load!
map
với puts
? Tôi không nhận được điểm nên làActiveRecord::Base.descendants.map(&:model_name)
Điều này dường như làm việc cho tôi:
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
@models = Object.subclasses_of(ActiveRecord::Base)
Rails chỉ tải các mô hình khi chúng được sử dụng, vì vậy dòng Dir.glob "yêu cầu" tất cả các tệp trong thư mục mô hình.
Khi bạn có các mô hình trong một mảng, bạn có thể làm những gì bạn đang nghĩ (ví dụ: trong mã xem):
<% @models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
...'/app/models/**/*.rb'
Trên một dòng: 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
Chỉ trong một dòng:
ActiveRecord::Base.subclasses.map(&:name)
Rails.application.eager_load!
trước khi thực hiện trong chế độ phát triển.
Tôi chưa thể bình luận, nhưng tôi nghĩ câu trả lời sj26 nên là câu trả lời hàng đầu. Chỉ là một gợi ý:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
Vâng, có nhiều cách bạn có thể tìm thấy tất cả các tên mô hình nhưng những gì tôi đã làm trong gem model_info của tôi là, nó sẽ cung cấp cho bạn tất cả các mô hình thậm chí có trong các viên đá quý.
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
sau đó chỉ cần in
@model_array
Để tránh tải trước tất cả Rails, bạn có thể làm điều này:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
allow_dependency (f) giống nhau Rails.application.eager_load!
sử dụng. Điều này sẽ tránh các lỗi tập tin đã được yêu cầu.
Sau đó, bạn có thể sử dụng tất cả các loại giải pháp để liệt kê các mô hình AR, như ActiveRecord::Base.descendants
Đây là một giải pháp đã được hiệu đính với ứng dụng Rails phức tạp (Quảng trường cung cấp năng lượng)
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
Nó lấy những phần tốt nhất của câu trả lời trong chủ đề này và kết hợp chúng trong giải pháp đơn giản và kỹ lưỡng nhất. Điều này xử lý các trường hợp mô hình của bạn nằm trong thư mục con, sử dụng set_table_name, v.v.
Chỉ cần bắt gặp cái này, vì tôi cần in tất cả các mô hình với các thuộc tính của chúng (được xây dựng trên nhận xét của @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}"}}
Điều này làm việc cho tôi. Đặc biệt cảm ơn tất cả các bài viết ở trên. Điều này sẽ trả về một bộ sưu tập của tất cả các mô hình của bạn.
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
Các Rails
dụng cụ phương pháp này descendants
, nhưng các mô hình không nhất thiết phải bao giờ thừa hưởng từ ActiveRecord::Base
, ví dụ, lớp bao gồm các mô-đunActiveModel::Model
sẽ có hành vi tương tự như một mô hình, chỉ cần không sẽ được liên kết với một bảng.
Vì vậy, bổ sung cho những gì các đồng nghiệp ở trên, nỗ lực nhỏ nhất sẽ làm điều này:
Monkey Patch của lớp Class
Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
và phương pháp models
, bao gồm cả tổ tiên, như sau:
Phương thức Module.constants
trả về (một cách hời hợt) một tập hợp symbols
, thay vì các hằng số, vì vậy, phương thức Array#select
có thể được thay thế như bản vá con khỉ này của 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
Khỉ vá của 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
Và, cuối cùng, phương pháp mô hình
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
Tôi đã thử rất nhiều câu trả lời trong số này không thành công trong Rails 4 (wow họ đã thay đổi một hoặc hai điều cho các vị thần) Tôi quyết định thêm câu trả lời của riêng mình. Những cái được gọi là ActiveRecord :: Base.connection và kéo tên bảng hoạt động nhưng không đạt được kết quả tôi muốn vì tôi đã ẩn một số mô hình (trong một thư mục bên trong ứng dụng / mô hình /) mà tôi không muốn xóa bỏ:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
Tôi đặt nó trong một bộ khởi tạo và có thể gọi nó từ bất cứ đâu. Ngăn chặn việc sử dụng chuột không cần thiết.
Giả sử tất cả các mô hình đều có trong ứng dụng / mô hình và bạn có grep & awk trên máy chủ của mình (phần lớn các trường hợp),
# 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")
Nó nhanh hơn Rails.application.eager_load!
hoặc lặp qua từng tệp với Dir
.
BIÊN TẬP:
Nhược điểm của phương pháp này là nó bỏ lỡ các mô hình gián tiếp kế thừa từ ActiveRecord (ví dụ FictionalBook < Book
). Cách chắc chắn nhất là Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name)
, mặc dù nó hơi chậm.
Tôi chỉ ném ví dụ này ở đây nếu có ai thấy nó hữu ích. Giải pháp dựa trên câu trả lời này https://stackoverflow.com/a/10712838/473040 .
Giả sử bạn có một cột public_uid
được sử dụng làm ID chính cho thế giới bên ngoài (bạn có thể tìm thấy lý do tại sao bạn muốn làm điều đó ở đây )
Bây giờ giả sử bạn đã giới thiệu trường này trên một loạt các Mô hình hiện có và bây giờ bạn muốn tạo lại tất cả các bản ghi chưa được thiết lập. Bạn có thể làm điều đó như thế này
# 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
bây giờ bạn có thể chạy rake di:public_uids:generate