rails-erd 0.4.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -103,6 +103,26 @@ module RailsERD
103
103
  end
104
104
  end
105
105
 
106
+ module Crowsfoot
107
+ include Simple
108
+ def relationship_style(relationship)
109
+ {}.tap do |options|
110
+ options[:style] = :dotted if relationship.indirect?
111
+
112
+ # Cardinality is "look-across".
113
+ dst = relationship.to_many? ? "crow" : "tee"
114
+ src = relationship.many_to? ? "crow" : "tee"
115
+
116
+ # Participation is "look-across".
117
+ dst << (relationship.destination_optional? ? "odot" : "tee")
118
+ src << (relationship.source_optional? ? "odot" : "tee")
119
+
120
+ options[:arrowsize] = 0.6
121
+ options[:arrowhead], options[:arrowtail] = dst, src
122
+ end
123
+ end
124
+ end
125
+
106
126
  module Bachman
107
127
  include Simple
108
128
  def relationship_style(relationship)
@@ -116,6 +136,7 @@ module RailsERD
116
136
  # Cardinality is "look-across".
117
137
  dst << "normal" if relationship.to_many?
118
138
  src << "normal" if relationship.many_to?
139
+
119
140
  options[:arrowsize] = 0.6
120
141
  options[:arrowhead], options[:arrowtail] = dst, src
121
142
  end
@@ -204,11 +225,11 @@ module RailsERD
204
225
  end
205
226
 
206
227
  def draw_node(name, options)
207
- graph.add_node escape_name(name), options
228
+ graph.add_nodes escape_name(name), options
208
229
  end
209
230
 
210
231
  def draw_edge(from, to, options)
211
- graph.add_edge graph.get_node(escape_name(from)), graph.get_node(escape_name(to)), options if node_exists?(from) and node_exists?(to)
232
+ graph.add_edges graph.get_node(escape_name(from)), graph.get_node(escape_name(to)), options if node_exists?(from) and node_exists?(to)
212
233
  end
213
234
 
214
235
  def escape_name(name)
@@ -31,7 +31,7 @@ module RailsERD
31
31
  # The type of the attribute, equal to the Rails migration type. Can be any
32
32
  # of +:string+, +:integer+, +:boolean+, +:text+, etc.
33
33
  def type
34
- column.type
34
+ column.type or column.sql_type.downcase.to_sym
35
35
  end
36
36
 
37
37
  # Returns +true+ if this attribute is a content column, that is, if it
@@ -100,12 +100,12 @@ module RailsERD
100
100
  # Returns any non-standard limit for this attribute. If a column has no
101
101
  # limit or uses a default database limit, this method returns +nil+.
102
102
  def limit
103
- column.limit if column.limit != @model.connection.native_database_types[type][:limit]
103
+ column.limit.to_i if column.limit != native_type[:limit] and column.limit.respond_to?(:to_i)
104
104
  end
105
105
 
106
106
  # Returns any non-standard scale for this attribute (decimal types only).
107
107
  def scale
108
- column.scale if column.scale != @model.connection.native_database_types[type][:scale]
108
+ column.scale.to_i if column.scale != native_type[:scale] and column.scale.respond_to?(:to_i)
109
109
  end
110
110
 
111
111
  # Returns a string that describes the limit for this attribute, such as
@@ -115,6 +115,12 @@ module RailsERD
115
115
  return "(#{limit},#{scale})" if limit and scale
116
116
  return "(#{limit})" if limit
117
117
  end
118
+
119
+ private
120
+
121
+ def native_type
122
+ @model.connection.native_database_types[type] or {}
123
+ end
118
124
  end
119
125
  end
120
126
  end
@@ -7,29 +7,29 @@ module RailsERD
7
7
  def from_models(domain, models) # @private :nodoc:
8
8
  (concrete_from_models(domain, models) + abstract_from_models(domain, models)).sort
9
9
  end
10
-
10
+
11
11
  private
12
-
12
+
13
13
  def concrete_from_models(domain, models)
14
14
  models.collect { |model| new(domain, model.name, model) }
15
15
  end
16
-
16
+
17
17
  def abstract_from_models(domain, models)
18
18
  models.collect(&:reflect_on_all_associations).flatten.collect { |association|
19
19
  association.options[:as].to_s.classify if association.options[:as]
20
20
  }.flatten.compact.uniq.collect { |name| new(domain, name) }
21
21
  end
22
22
  end
23
-
23
+
24
24
  extend Inspectable
25
25
  inspection_attributes :model
26
-
26
+
27
27
  # The domain in which this entity resides.
28
28
  attr_reader :domain
29
-
29
+
30
30
  # The Active Record model that this entity corresponds to.
31
31
  attr_reader :model
32
-
32
+
33
33
  # The name of this entity. Equal to the class name of the corersponding
34
34
  # model (for concrete entities) or given name (for abstract entities).
35
35
  attr_reader :name
@@ -37,12 +37,12 @@ module RailsERD
37
37
  def initialize(domain, name, model = nil) # @private :nodoc:
38
38
  @domain, @name, @model = domain, name, model
39
39
  end
40
-
40
+
41
41
  # Returns an array of attributes for this entity.
42
42
  def attributes
43
43
  @attributes ||= if generalized? then [] else Attribute.from_model(domain, model) end
44
44
  end
45
-
45
+
46
46
  # Returns an array of all relationships that this entity has with other
47
47
  # entities in the domain model.
48
48
  def relationships
@@ -60,7 +60,7 @@ module RailsERD
60
60
  def disconnected?
61
61
  relationships.none?
62
62
  end
63
-
63
+
64
64
  # Returns +true+ if this entity is a generalization, which does not
65
65
  # correspond with a database table. Generalized entities are constructed
66
66
  # from polymorphic interfaces. Any +has_one+ or +has_many+ association
@@ -69,7 +69,7 @@ module RailsERD
69
69
  def generalized?
70
70
  !model
71
71
  end
72
-
72
+
73
73
  # Returns +true+ if this entity descends from another entity, and is
74
74
  # represented in the same table as its parent. In Rails this concept is
75
75
  # referred to as single-table inheritance. In entity-relationship
@@ -77,23 +77,23 @@ module RailsERD
77
77
  def specialized?
78
78
  !generalized? and !model.descends_from_active_record?
79
79
  end
80
-
80
+
81
81
  # Returns +true+ if this entity does not correspond directly with a
82
82
  # database table (if and only if the entity is specialized or
83
83
  # generalized).
84
84
  def abstract?
85
85
  specialized? or generalized?
86
86
  end
87
-
87
+
88
88
  # Returns all child entities, if this is a generalized entity.
89
89
  def children
90
90
  @children ||= domain.specializations_by_entity_name(name).map(&:specialized)
91
91
  end
92
-
92
+
93
93
  def to_s # @private :nodoc:
94
94
  name
95
95
  end
96
-
96
+
97
97
  def <=>(other) # @private :nodoc:
98
98
  self.name <=> other.name
99
99
  end
@@ -21,10 +21,20 @@ module RailsERD
21
21
  private
22
22
 
23
23
  def association_identity(association)
24
- identifier = association.options[:join_table] || association.options[:through] || association.send(Domain.foreign_key_method_name).to_s
24
+ identifier = association_identifier(association)
25
25
  Set[identifier, association_owner(association), association_target(association)]
26
26
  end
27
27
 
28
+ def association_identifier(association)
29
+ if association.macro == :has_and_belongs_to_many
30
+ # Rails 4+ supports the join_table method, and doesn't expose it
31
+ # as an option if it's an implicit default.
32
+ (association.respond_to?(:join_table) && association.join_table) || association.options[:join_table]
33
+ else
34
+ association.options[:through] || association.send(Domain.foreign_key_method_name).to_s
35
+ end
36
+ end
37
+
28
38
  def association_owner(association)
29
39
  association.options[:as] ? association.options[:as].to_s.classify : association.active_record.name
30
40
  end
@@ -4,9 +4,9 @@ module RailsERD
4
4
  class Cardinality
5
5
  extend Inspectable
6
6
  inspection_attributes :source_range, :destination_range
7
-
7
+
8
8
  N = Infinity = 1.0/0 # And beyond.
9
-
9
+
10
10
  CLASSES = {
11
11
  [1, 1] => :one_to_one,
12
12
  [1, N] => :one_to_many,
@@ -16,7 +16,7 @@ module RailsERD
16
16
 
17
17
  # Returns a range that indicates the source (left) cardinality.
18
18
  attr_reader :source_range
19
-
19
+
20
20
  # Returns a range that indicates the destination (right) cardinality.
21
21
  attr_reader :destination_range
22
22
 
@@ -26,7 +26,7 @@ module RailsERD
26
26
  @source_range = compose_range(source_range)
27
27
  @destination_range = compose_range(destination_range)
28
28
  end
29
-
29
+
30
30
  # Returns the name of this cardinality, based on its two cardinal
31
31
  # numbers (for source and destination). Can be any of
32
32
  # +:one_to_one:+, +:one_to_many+, or +:many_to_many+. The name
@@ -44,23 +44,23 @@ module RailsERD
44
44
  def name
45
45
  CLASSES[cardinality_class]
46
46
  end
47
-
47
+
48
48
  # Returns +true+ if the source (left side) is not mandatory.
49
49
  def source_optional?
50
50
  source_range.first < 1
51
51
  end
52
-
52
+
53
53
  # Returns +true+ if the destination (right side) is not mandatory.
54
54
  def destination_optional?
55
55
  destination_range.first < 1
56
56
  end
57
-
57
+
58
58
  # Returns the inverse cardinality. Destination becomes source, source
59
59
  # becomes destination.
60
60
  def inverse
61
61
  self.class.new destination_range, source_range
62
62
  end
63
-
63
+
64
64
  CLASSES.each do |cardinality_class, name|
65
65
  class_eval <<-RUBY
66
66
  def #{name}?
@@ -68,11 +68,11 @@ module RailsERD
68
68
  end
69
69
  RUBY
70
70
  end
71
-
71
+
72
72
  def ==(other) # @private :nodoc:
73
73
  source_range == other.source_range and destination_range == other.destination_range
74
74
  end
75
-
75
+
76
76
  def <=>(other) # @private :nodoc:
77
77
  (cardinality_class <=> other.cardinality_class).nonzero? or
78
78
  compare_with(other) { |x| x.source_range.first + x.destination_range.first }.nonzero? or
@@ -80,7 +80,7 @@ module RailsERD
80
80
  compare_with(other) { |x| x.source_range.last }.nonzero? or
81
81
  compare_with(other) { |x| x.destination_range.last }
82
82
  end
83
-
83
+
84
84
  # Returns an array with the cardinality classes for the source and
85
85
  # destination of this cardinality. Possible return values are:
86
86
  # <tt>[1, 1]</tt>, <tt>[1, N]</tt>, <tt>[N, N]</tt>, and (in theory)
@@ -88,21 +88,21 @@ module RailsERD
88
88
  def cardinality_class
89
89
  [source_cardinality_class, destination_cardinality_class]
90
90
  end
91
-
91
+
92
92
  protected
93
93
 
94
94
  # The cardinality class of the source (left side). Either +1+ or +Infinity+.
95
95
  def source_cardinality_class
96
96
  source_range.last == 1 ? 1 : N
97
97
  end
98
-
98
+
99
99
  # The cardinality class of the destination (right side). Either +1+ or +Infinity+.
100
100
  def destination_cardinality_class
101
101
  destination_range.last == 1 ? 1 : N
102
102
  end
103
-
103
+
104
104
  private
105
-
105
+
106
106
  def compose_range(r)
107
107
  return r..r if r.kind_of?(Integer) && r > 0
108
108
  return (r.begin)..(r.end - 1) if r.exclude_end?
@@ -8,9 +8,9 @@ module RailsERD
8
8
  def from_models(domain, models) # @private :nodoc:
9
9
  (inheritance_from_models(domain, models) + polymorphic_from_models(domain, models)).sort
10
10
  end
11
-
11
+
12
12
  private
13
-
13
+
14
14
  def polymorphic_from_models(domain, models)
15
15
  models.collect(&:reflect_on_all_associations).flatten.collect { |association|
16
16
  [association.options[:as].to_s.classify, association.active_record.name] if association.options[:as]
@@ -18,14 +18,14 @@ module RailsERD
18
18
  new(domain, domain.entity_by_name(names.first), domain.entity_by_name(names.last))
19
19
  }
20
20
  end
21
-
21
+
22
22
  def inheritance_from_models(domain, models)
23
23
  models.reject(&:descends_from_active_record?).collect { |model|
24
24
  new(domain, domain.entity_by_name(model.base_class.name), domain.entity_by_name(model.name))
25
25
  }
26
26
  end
27
27
  end
28
-
28
+
29
29
  extend Inspectable
30
30
  inspection_attributes :generalized, :specialized
31
31
 
@@ -34,18 +34,18 @@ module RailsERD
34
34
 
35
35
  # The source entity.
36
36
  attr_reader :generalized
37
-
37
+
38
38
  # The destination entity.
39
39
  attr_reader :specialized
40
-
40
+
41
41
  def initialize(domain, generalized, specialized) # @private :nodoc:
42
42
  @domain, @generalized, @specialized = domain, generalized, specialized
43
43
  end
44
-
44
+
45
45
  def inheritance?
46
46
  !polymorphic?
47
47
  end
48
-
48
+
49
49
  def polymorphic?
50
50
  generalized.generalized?
51
51
  end
@@ -0,0 +1,4 @@
1
+ module RailsERD
2
+ VERSION = "1.0.0"
3
+ BANNER = "RailsERD #{VERSION}"
4
+ end
data/test/test_helper.rb CHANGED
@@ -14,7 +14,7 @@ class ActiveSupport::TestCase
14
14
 
15
15
  def create_table(table, columns = {}, pk = nil)
16
16
  opts = if pk then { :primary_key => pk } else { :id => false } end
17
- ActiveRecord::Schema.define do
17
+ ActiveRecord::Schema.instance_eval do
18
18
  suppress_messages do
19
19
  create_table table, opts do |t|
20
20
  columns.each do |column, type|
@@ -23,14 +23,16 @@ class ActiveSupport::TestCase
23
23
  end
24
24
  end
25
25
  end
26
+ ActiveRecord::Base.clear_cache!
26
27
  end
27
-
28
+
28
29
  def add_column(*args)
29
- ActiveRecord::Schema.define do
30
+ ActiveRecord::Schema.instance_eval do
30
31
  suppress_messages do
31
32
  add_column *args
32
33
  end
33
34
  end
35
+ ActiveRecord::Base.clear_cache!
34
36
  end
35
37
 
36
38
  def create_model(name, *args, &block)
@@ -42,13 +44,13 @@ class ActiveSupport::TestCase
42
44
  create_table Object.const_get(name.to_sym).table_name, columns, Object.const_get(name.to_sym).primary_key rescue nil
43
45
  end
44
46
  end
45
-
47
+
46
48
  def create_models(*names)
47
49
  names.each do |name|
48
50
  create_model name
49
51
  end
50
52
  end
51
-
53
+
52
54
  def collect_stdout
53
55
  stdout = $stdout
54
56
  $stdout = StringIO.new
@@ -58,14 +60,14 @@ class ActiveSupport::TestCase
58
60
  ensure
59
61
  $stdout = stdout
60
62
  end
61
-
63
+
62
64
  def create_simple_domain
63
65
  create_model "Beer", :bar => :references do
64
66
  belongs_to :bar
65
67
  end
66
68
  create_model "Bar"
67
69
  end
68
-
70
+
69
71
  def create_one_to_one_assoc_domain
70
72
  create_model "One" do
71
73
  has_one :other
@@ -93,12 +95,12 @@ class ActiveSupport::TestCase
93
95
  end
94
96
  create_table "manies_mores", :many_id => :integer, :more_id => :integer
95
97
  end
96
-
98
+
97
99
  def create_specialization
98
100
  create_model "Beverage", :type => :string
99
101
  Object.const_set :Beer, Class.new(Beverage)
100
102
  end
101
-
103
+
102
104
  def create_generalization
103
105
  create_model "Cannon"
104
106
  create_model "Galleon" do
@@ -107,18 +109,19 @@ class ActiveSupport::TestCase
107
109
  end
108
110
 
109
111
  private
110
-
112
+
111
113
  def reset_domain
112
114
  if defined? ActiveRecord
113
115
  ActiveRecord::Base.descendants.each do |model|
116
+ model.reset_column_information
114
117
  Object.send :remove_const, model.name.to_sym
115
118
  end
116
119
  ActiveRecord::Base.connection.tables.each do |table|
117
120
  ActiveRecord::Base.connection.drop_table table
118
121
  end
119
122
  ActiveRecord::Base.direct_descendants.clear
120
- Arel::Relation.send :class_variable_set, :@@connection_tables_primary_keys, {}
121
123
  ActiveSupport::Dependencies::Reference.clear!
124
+ ActiveRecord::Base.clear_cache!
122
125
  end
123
126
  end
124
127
  end