class ModelsDiagram

RailRoady models diagram

Public Class Methods

new(options = OptionsStruct.new) click to toggle source
Calls superclass method AppDiagram::new
# File lib/railroady/models_diagram.rb, line 5
def initialize(options = OptionsStruct.new)
  super options
  @graph.diagram_type = 'Models'
  # Processed habtm associations
  @habtm = []
end

Public Instance Methods

engine_files() click to toggle source
# File lib/railroady/models_diagram.rb, line 33
def engine_files
  engines.collect { |engine| Dir.glob("#{engine.root}/app/models/**/*.rb") }.flatten
end
extract_class_name(filename) click to toggle source
# File lib/railroady/models_diagram.rb, line 37
def extract_class_name(filename)
  filename_was = filename
  class_name = nil

  filename = "app/models/#{filename.split('app/models')[1]}"

  while filename.split('/').length > 2
    begin
      class_name = filename.match(%r{.*/models/(.*).rb$})[1]
      class_name = class_name.camelize
      # class_name = class_name.from 2 if class_name.start_with? '::'
      class_name.constantize
      break
    rescue NoMethodError
      class_name = nil
      filename_end = filename.split('/')[2..-1]
      filename_end.shift
      filename = "#{filename.split('/')[0, 2].join('/')}/#{filename_end.join('/')}"
    end
  end

  if class_name.nil?
    filename_was.match(%r{.*/models/(.*).rb$})[1].camelize
  else
    warn class_name
    class_name
  end
end
generate() click to toggle source

Process model files

# File lib/railroady/models_diagram.rb, line 13
def generate
  warn 'Generating models diagram' if @options.verbose
  get_files.each do |f|
    begin
      process_class extract_class_name(f).constantize
    rescue NoMethodError
      warn "Warning: exception #{$ERROR_INFO} raised while trying to load model class #{f}"
    end
  end
end
get_files(prefix = '') click to toggle source
# File lib/railroady/models_diagram.rb, line 24
def get_files(prefix = '')
  files = !@options.specify.empty? ? Dir.glob(@options.specify) : Dir.glob("#{prefix}app/models/**/*.rb")
  files += Dir.glob('vendor/plugins/**/app/models/*.rb') if @options.plugins_models
  files -= Dir.glob("#{prefix}app/models/concerns/**/*.rb") unless @options.include_concerns
  files += engine_files if @options.engine_models
  files -= Dir.glob(@options.exclude)
  files
end
include_inheritance?(current_class) click to toggle source
# File lib/railroady/models_diagram.rb, line 91
def include_inheritance?(current_class)
  warn current_class.superclass if @options.verbose
  (defined?(ActiveRecord::Base) ? current_class.superclass != ActiveRecord::Base : true) &&
    (defined?(CouchRest::Model::Base) ? current_class.superclass != CouchRest::Model::Base : true) &&
    (current_class.superclass != Object)
end
process_active_record_model(current_class) click to toggle source
# File lib/railroady/models_diagram.rb, line 109
def process_active_record_model(current_class)
  node_attribs = []
  if @options.brief || current_class.abstract_class?
    node_type = 'model-brief'
  else
    node_type = 'model'

    # Collect model's content columns
    # content_columns = current_class.content_columns

    if @options.hide_magic
      # From patch #13351
      # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
      magic_fields = %w[created_at created_on updated_at updated_on lock_version type id position parent_id lft rgt quote template]
      magic_fields << "#{current_class.table_name}_count" if current_class.respond_to? 'table_name'
      content_columns = current_class.content_columns.reject { |c| magic_fields.include? c.name }
    else
      content_columns = current_class.columns
    end

    content_columns.each do |a|
      content_column = a.name
      content_column += " :#{a.sql_type.to_s}" unless @options.hide_types
      node_attribs << content_column
    end
  end
  @graph.add_node [node_type, current_class.name, node_attribs]

  # Process class associations
  associations = current_class.reflect_on_all_associations
  if @options.inheritance && ! @options.transitive
    superclass_associations = current_class.superclass.reflect_on_all_associations

    associations = associations.reject { |a| superclass_associations.include? a }
    # This doesn't works!
    # associations -= current_class.superclass.reflect_on_all_associations
  end

  associations.each do |a|
    process_association current_class.name, a
  end

  true
end
process_association(class_name, assoc) click to toggle source

Process a model association

# File lib/railroady/models_diagram.rb, line 269
def process_association(class_name, assoc)
  warn "- Processing model association #{assoc.name}" if @options.verbose
  # Skip "belongs_to" associations
  macro = assoc.methods.to_s
  return if %w[belongs_to referenced_in].include?(macro) && !@options.show_belongs_to

  # Skip "through" associations
  through = assoc.options.include?(:through)
  return if through && @options.hide_through

  # TODO:
  # FAIL: assoc.methods.include?(:class_name)
  # FAIL: assoc.responds_to?(:class_name)
  assoc_class_name = assoc.class_name rescue nil
  assoc_class_name ||= assoc.name.to_s.underscore.singularize.camelize

  # Only non standard association names needs a label

  # from patch #12384
  # if assoc.class_name == assoc.name.to_s.singularize.camelize
  if assoc_class_name == assoc.name.to_s.singularize.camelize
    assoc_name = ''
  else
    assoc_name = assoc.name.to_s
  end

  # Patch from "alpack" to support classes in a non-root module namespace. See: http://disq.us/yxl1v
  if class_name.include?('::') && !assoc_class_name.include?('::')
    assoc_class_name = class_name.split('::')[0..-2].push(assoc_class_name).join('::')
  end
  assoc_class_name = assoc_class_name.gsub(/^::/, '')

  if %w[has_one references_one embeds_one].include?(macro)
    assoc_type = 'one-one'
  elsif macro == 'has_many' && (!assoc.options[:through]) ||
        %w[references_many embeds_many].include?(macro)
    assoc_type = 'one-many'
  elsif macro == 'belongs_to'
    assoc_type = 'belongs-to'
  else # habtm or has_many, :through
    # Add FAKE associations too in order to understand mistakes
    return if @habtm.include? [assoc_class_name, class_name, assoc_name]

    assoc_type = 'many-many'
    @habtm << [class_name, assoc_class_name, assoc_name]
  end
  # from patch #12384
  # @graph.add_edge [assoc_type, class_name, assoc.class_name, assoc_name]
  @graph.add_edge [assoc_type, class_name, assoc_class_name, assoc_name]
end
process_basic_class(current_class) click to toggle source
# File lib/railroady/models_diagram.rb, line 98
def process_basic_class(current_class)
  node_type = @options.brief ? 'class-brief' : 'class'
  @graph.add_node [node_type, current_class.name]
  true
end
process_basic_module(current_class) click to toggle source
# File lib/railroady/models_diagram.rb, line 104
def process_basic_module(current_class)
  @graph.add_node ['module', current_class.name]
  false
end
process_class(current_class) click to toggle source

Process a model class

# File lib/railroady/models_diagram.rb, line 67
def process_class(current_class)
  warn "Processing #{current_class}" if @options.verbose

  generated =
    if defined?(CouchRest::Model::Base) && current_class.new.is_a?(CouchRest::Model::Base)
      process_couchrest_model(current_class)
    elsif defined?(Mongoid::Document) && current_class.new.is_a?(Mongoid::Document)
      process_mongoid_model(current_class)
    elsif defined?(DataMapper::Resource) && current_class.new.is_a?(DataMapper::Resource)
      process_datamapper_model(current_class)
    elsif current_class.respond_to? 'reflect_on_all_associations'
      process_active_record_model(current_class)
    elsif @options.all && (current_class.is_a? Class)
      process_basic_class(current_class)
    elsif @options.modules && (current_class.is_a? Module)
      process_basic_module(current_class)
    end

  warn generated
  if @options.inheritance && generated && include_inheritance?(current_class)
    @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
  end
end
process_couchrest_model(current_class) click to toggle source

Some very basic CouchRest::Model support

Field types note: the field's type is rendered only if it's explicitly specified in a model.

# File lib/railroady/models_diagram.rb, line 240
def process_couchrest_model(current_class)
  node_attribs = []

  if @options.brief
    node_type = 'model-brief'
  else
    node_type = 'model'

    # Collect model's content columns
    content_columns = current_class.properties

    if @options.hide_magic
      magic_fields = %w[created_at updated_at type _id _rev]
      content_columns = content_columns.reject { |c| magic_fields.include?(c.name) }
    end

    content_columns.each do |a|
      content_column = a.name
      content_column += " :#{a.type}" unless @options.hide_types || a.type.nil?
      node_attribs << content_column
    end
  end

  @graph.add_node [node_type, current_class.name, node_attribs]

  true
end
process_datamapper_model(current_class) click to toggle source
# File lib/railroady/models_diagram.rb, line 154
def process_datamapper_model(current_class)
  node_attribs = []
  if @options.brief # || current_class.abstract_class?
    node_type = 'model-brief'
  else
    node_type = 'model'

    # Collect model's properties
    props = current_class.properties.sort_by(&:name)

    if @options.hide_magic
      # From patch #13351
      # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
      magic_fields =
        %w[created_at created_on updated_at updated_on lock_version _type _id position parent_id lft rgt quote template]
      props = props.reject { |c| magic_fields.include?(c.name.to_s) }
    end

    props.each do |a|
      prop = a.name.to_s
      prop += " :#{a.class.name.split('::').last}" unless @options.hide_types
      node_attribs << prop
    end
  end
  @graph.add_node [node_type, current_class.name, node_attribs]

  # Process relationships
  relationships = current_class.relationships

  # TODO: Manage inheritance

  relationships.each do |a|
    process_datamapper_relationship current_class.name, a
  end

  true
end
process_datamapper_relationship(class_name, relation) click to toggle source

Process a DataMapper relationship

# File lib/railroady/models_diagram.rb, line 321
def process_datamapper_relationship(class_name, relation)
  warn "- Processing DataMapper model relationship #{relation.name}" if @options.verbose

  # Skip "belongs_to" relationships
  dm_type = relation.class.to_s.split('::')[-2]
  return if dm_type == 'ManyToOne' && !@options.show_belongs_to

  dm_parent_model = relation.parent_model.to_s
  dm_child_model = relation.child_model.to_s

  assoc_class_name = ''
  # Get the assoc_class_name
  if dm_parent_model.eql?(class_name)
    assoc_class_name = dm_child_model
  else
    assoc_class_name = dm_parent_model
  end

  # Only non standard association names needs a label
  assoc_name = ''
  unless relation.name.to_s.singularize.camelize.eql?(assoc_class_name.split('::').last)
    assoc_name = relation.name.to_s
  end

  # TO BE IMPROVED
  rel_type = 'many-many' # default value for ManyToOne and ManyToMany
  if dm_type == 'OneToOne'
    rel_type = 'one-one'
  elsif dm_type == 'OneToMany'
    rel_type = 'one-many'
  end

  @graph.add_edge [rel_type, class_name, assoc_class_name, assoc_name]
end
process_mongoid_model(current_class) click to toggle source
# File lib/railroady/models_diagram.rb, line 192
def process_mongoid_model(current_class)
  node_attribs = []

  if @options.brief
    node_type = 'model-brief'
  else
    node_type = 'model'

    # Collect model's content columns
    content_columns = current_class.fields.values.sort_by(&:name)

    if @options.hide_magic
      # From patch #13351
      # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
      magic_fields = %w[created_at created_on updated_at updated_on lock_version _type _id position parent_id lft rgt quote template]
      content_columns = content_columns.reject { |c| magic_fields.include?(c.name) }
    end

    content_columns.each do |a|
      content_column = a.name
      content_column += " :#{a.type}" unless @options.hide_types
      node_attribs << content_column
    end
  end

  @graph.add_node [node_type, current_class.name, node_attribs]

  # Process class associations
  associations = current_class.relations.values

  if @options.inheritance && !@options.transitive &&
     current_class.superclass.respond_to?(:relations)
    associations -= current_class.superclass.relations.values
  end

  associations.each do |a|
    process_association current_class.name, a
  end

  true
end