require "set" require "rails_erd" require "rails_erd/entity" require "rails_erd/relationship" require "rails_erd/relationship/cardinality" require "rails_erd/attribute" module RailsERD # The domain describes your Rails domain model. This class is the starting # point to get information about your models. class Domain class << self # Generates a domain model object based on all loaded subclasses of # ActiveRecord::Base. Make sure your models are loaded before calling # this method. # # The +options+ hash allows you to override the default options. For a # list of available options, see RailsERD. def generate(options = {}) new ActiveRecord::Base.descendants, options end end # The options that are used to generate this domain model. attr_reader :options # Create a new domain model object based on the given array of models. # The given models are assumed to be subclasses of ActiveRecord::Base. def initialize(models = [], options = {}) @models, @options = models, RailsERD.options.merge(options) end # Returns the domain model name, which is the name of your Rails # application or +nil+ outside of Rails. def name defined? Rails and Rails.application and Rails.application.class.parent.name end # Returns all entities of your domain model. def entities @entities ||= entity_mapping.values.sort end # Returns all relationships in your domain model. def relationships @relationships ||= Relationship.from_associations(self, associations) end # Returns a specific entity object for the given Active Record model. def entity_for(model) # @private :nodoc: entity_mapping[model] or raise "model #{model} exists, but is not included in the domain" end # Returns an array of relationships for the given Active Record model. def relationships_for(model) # @private :nodoc: relationships_mapping[model] or [] end def inspect # @private :nodoc: "#<#{self.class}:0x%.14x {%s}>" % [object_id << 1, relationships.map { |rel| "#{rel.source} => #{rel.destination}" } * ", "] end private def entity_mapping @entity_mapping ||= Hash[@models.collect { |model| [model, Entity.new(self, model)] }] end def relationships_mapping @relationships_mapping ||= {}.tap do |mapping| relationships.each do |relationship| (mapping[relationship.source.model] ||= []) << relationship (mapping[relationship.destination.model] ||= []) << relationship end end end def associations @associations ||= @models.collect(&:reflect_on_all_associations).flatten.select { |assoc| check_association_validity(assoc) } end def check_association_validity(association) # Raises an ActiveRecord::ActiveRecordError if the association is broken. association.check_validity! # Raises NameError if the associated class cannot be found. model = association.klass # Raises error if model is not in the domain. entity_for model rescue => e warn "Invalid association #{association_description(association)} (#{e.message})" end def warn(message) puts "Warning: #{message}" unless options.suppress_warnings end def association_description(association) "#{association.name.inspect} on #{association.active_record}" end end end