rails-erd 0.4.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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