lib/modaldiagrams/modaldiagrams.rb in modaldiagrams-1.0.1 vs lib/modaldiagrams/modaldiagrams.rb in modaldiagrams-1.1.0
- old
+ new
@@ -1,8 +1,11 @@
# ActiveRecord DB Diagrams
# Configuration parameters can be changed by writing a file named config/modal_diagrams.yml
+require 'modalsettings'
+require 'modalsupport'
+
module ModalDiagrams
# Field type abbreviations
TYPE = {
:date=>'d',
@@ -41,12 +44,18 @@
rep = [] # this is to avoid multiple relations between two models
# and the next is to draw separate diagrams per cluster
cluster_classes = {} # assoc cluster name to array of class names
relation_classes = [] # each element is the 2-element array of class names of the corresponding relation in relations
- models = dbmodels
+ model_selection_options = {
+ :all_models => cfg.include_all_models,
+ :dynamic_models => cfg.include_dynamic_models,
+ :include_files => false
+ }
+ models = dbmodels(model_selection_options.merge(:exclude_sti_models => true))
+
models.each do |cls|
if cls.respond_to?(:reflect_on_all_associations) && ActiveRecord::Base.connection.table_exists?(cls.table_name)
# Note: Don't use content_columns ignores columns ending with _id which I use for enum fields
columns = cls.columns.reject { |c| c.primary || c.name =~ /(_count)$/ || c.name == cls.inheritance_column || c.name =~ /^(created_at|updated_at)$/ }.map{|c| "#{c.name} : #{TYPE[c.type]}"}
columns_to_ignore = cls.reflect_on_all_associations.map{|a|
@@ -142,10 +151,11 @@
end
end
end
if cfg.show_sti
+ sti_classes = dbmodels(model_selection_options.merge(:exclude_non_sti_models => true))
sti_classes.each do |sti_class|
cls = sti_class.base_class
if cls.respond_to?(:cluster)
cluster = cls.cluster.to_s
else
@@ -200,29 +210,77 @@
end
private
- # return ActiveRecord classes corresponding to tables, without STI derived classes, but including indirectly
- # derived classes that do have their own tables (to achieve this we use the convention that in such cases
- # the base class, directly derived from ActiveRecord::Base has a nil table_name)
- def dbmodels
- models = Dir.glob(File.join(Rails.root,"app/models/**/*.rb"))\
- .map{|f| File.basename(f).chomp(".rb").camelize.constantize}\
- .select{|c| has_table(c)}\
- .reject{|c| has_table(c.superclass)}
- models += ActiveRecord::Base.send(:subclasses).reject{|c| c.name.starts_with?('CGI::') || !has_table(c) || has_table(c.superclass)}
- models.uniq
- end
+ # Return the database models
+ # Options:
+ # :all_models # Return also models in plugins, not only in the app (app/models)
+ # :dynamic_models # Return dynamically defined models too (not defined in a model file)
+ # :exclude_sti_models # Exclude derived (STI) models
+ # :exclude_non_sti_models # Exclude top level models
+ # :include_files # Return also the model definition file pathnames (return pairs of [model, file])
+ # :only_app_files # But return nil for files not in the app proper
+ # :only_app_tree_files # But return nil for files not in the app directory tree (app, vendor...)
+ def dbmodels(options={})
- def sti_classes
- models = Dir.glob(File.join(Rails.root,"app/models/**/*.rb"))\
- .map{|f| File.basename(f).chomp(".rb").camelize.constantize}\
- .select{|c| has_table(c) && c.base_class!=c}
- models += ActiveRecord::Base.send(:subclasses).reject{|c| c.name.starts_with?('CGI::') || !has_table(c)}.select{|c| has_table(c) && c.base_class!=c}
- models.uniq
+ models_dir = 'app/models'
+ if Rails.respond_to?(:application)
+ models_dir = Rails.application.paths[models_dir]
+ end
+ models_dir = Rails.root.join(models_dir)
+
+ if options[:all_models]
+ # Include also models from plugins
+ model_dirs = $:.grep(/\/models\/?\Z/)
+ else
+ # Only main application models
+ model_dirs = [models_dir]
+ end
+
+ models = []
+ files = {}
+ model_dirs.each do |base|
+ Dir.glob(File.join(base,"**/*.rb")).each do |fn|
+ model = File.basename(fn).chomp(".rb").camelize.constantize
+ models << model
+ files[model.to_s] = fn
+ end
+ end
+ models = models.sort_by{|m| m.to_s}
+
+ if options[:dynamic_models]
+ # Now add dynamically generated models (not having dedicated files)
+ # note that subclasses of these models are not added here
+ models += ActiveRecord::Base.send(:subclasses)
+ models = models.uniq
+ end
+
+ models = models.uniq.reject{|model| !has_table?(model)}
+
+ non_sti_models, sti_models = models.partition{|model| model.base_class==model}
+
+ models = []
+ models += non_sti_models unless options[:exclude_non_sti_models]
+ models += sti_models unless options[:exclude_sti_models]
+ if options[:include_files]
+ models = models.map{|model| [model, files[model.to_s]]}
+ if options[:only_app_files] || options[:only_app_tree_files]
+ if options[:only_app_files]
+ suffix = models_dir.to_s
+ else
+ suffix = Rails.root.to_s
+ end
+ suffix += '/' unless suffix.ends_with?('/')
+ models = models.map{|model, file| [model, file && (file.starts_with?(suffix) ? file : nil)]}
+ end
+ end
+ models
end
+ def has_table?(cls)
+ (cls != ActiveRecord::Base) && cls.respond_to?(:table_name) && cls.table_name.present?
+ end
def assoc_foreign_key(assoc)
# Up to ActiveRecord 3.1 we had primary_key_name in AssociationReflection; not it is foreign_key
assoc.respond_to?(:primary_key_name) ? assoc.primary_key_name : assoc.foreign_key
end