rails-erd 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGES.rdoc +17 -1
- data/Gemfile +3 -2
- data/Gemfile.lock +8 -4
- data/README.md +60 -0
- data/Rakefile +10 -50
- data/VERSION +1 -1
- data/lib/rails_erd.rb +28 -1
- data/lib/rails_erd/diagram.rb +66 -33
- data/lib/rails_erd/diagram/graphviz.rb +123 -92
- data/lib/rails_erd/diagram/templates/node.erb +2 -2
- data/lib/rails_erd/domain.rb +51 -23
- data/lib/rails_erd/domain/attribute.rb +102 -0
- data/lib/rails_erd/domain/entity.rb +102 -0
- data/lib/rails_erd/domain/relationship.rb +189 -0
- data/lib/rails_erd/domain/relationship/cardinality.rb +118 -0
- data/lib/rails_erd/domain/specialization.rb +58 -0
- data/lib/rails_erd/railtie.rb +1 -1
- data/rails-erd.gemspec +19 -16
- data/test/test_helper.rb +21 -5
- data/test/unit/attribute_test.rb +35 -8
- data/test/unit/cardinality_test.rb +41 -35
- data/test/unit/diagram_test.rb +130 -43
- data/test/unit/domain_test.rb +131 -8
- data/test/unit/entity_test.rb +150 -46
- data/test/unit/graphviz_test.rb +52 -14
- data/test/unit/rake_task_test.rb +2 -2
- data/test/unit/relationship_test.rb +73 -24
- data/test/unit/specialization_test.rb +57 -0
- metadata +15 -13
- data/README.rdoc +0 -51
- data/lib/rails_erd/attribute.rb +0 -95
- data/lib/rails_erd/entity.rb +0 -73
- data/lib/rails_erd/relationship.rb +0 -177
- data/lib/rails_erd/relationship/cardinality.rb +0 -118
@@ -19,13 +19,10 @@ end
|
|
19
19
|
module RailsERD
|
20
20
|
class Diagram
|
21
21
|
# Create Graphviz-based diagrams based on the domain model. For easy
|
22
|
-
# command line graph generation, you can use
|
22
|
+
# command line graph generation, you can use:
|
23
23
|
#
|
24
24
|
# % rake erd
|
25
25
|
#
|
26
|
-
# Please see the README.rdoc file for more details on how to use Rails ERD
|
27
|
-
# from the command line.
|
28
|
-
#
|
29
26
|
# === Options
|
30
27
|
#
|
31
28
|
# The following options are supported:
|
@@ -37,7 +34,7 @@ module RailsERD
|
|
37
34
|
# worse than a PDF file. The available formats depend on your installation
|
38
35
|
# of Graphviz.
|
39
36
|
# notation:: The cardinality notation to be used. Can be +:simple+ or
|
40
|
-
# +:
|
37
|
+
# +:bachman+. Refer to README.rdoc or to the examples on the project
|
41
38
|
# homepage for more information and examples.
|
42
39
|
# orientation:: The direction of the hierarchy of entities. Either +:horizontal+
|
43
40
|
# or +:vertical+. Defaults to +horizontal+. The orientation of the
|
@@ -60,9 +57,7 @@ module RailsERD
|
|
60
57
|
:concentrate => true,
|
61
58
|
:labelloc => :t,
|
62
59
|
:fontsize => 13,
|
63
|
-
:fontname => "Arial Bold"
|
64
|
-
:remincross => true,
|
65
|
-
:outputorder => :edgesfirst
|
60
|
+
:fontname => "Arial Bold"
|
66
61
|
}
|
67
62
|
|
68
63
|
# Default node attributes.
|
@@ -86,94 +81,139 @@ module RailsERD
|
|
86
81
|
:fontsize => 7
|
87
82
|
}
|
88
83
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
options[:arrowhead] = relationship.to_many? ? :normal : :none
|
94
|
-
options[:arrowtail] = relationship.many_to? ? :normal : :none
|
95
|
-
},
|
96
|
-
|
97
|
-
# Closed arrow for to/from many, UML ranges at each end.
|
98
|
-
:uml => lambda { |relationship, options|
|
99
|
-
options[:arrowsize] = 0.7
|
100
|
-
options[:arrowhead] = relationship.to_many? ? :vee : :none
|
101
|
-
options[:arrowtail] = relationship.many_to? ? :vee : :none
|
102
|
-
ranges = [relationship.cardinality.destination_range, relationship.cardinality.source_range].map do |range|
|
103
|
-
if range.min == range.max
|
104
|
-
"#{range.min}"
|
105
|
-
else
|
106
|
-
"#{range.min}..#{range.max == Relationship::Cardinality::Infinity ? "∗" : range.max}"
|
107
|
-
end
|
84
|
+
module Simple
|
85
|
+
def entity_style(entity, attributes)
|
86
|
+
{}.tap do |options|
|
87
|
+
options[:fontcolor] = options[:color] = :grey60 if entity.abstract?
|
108
88
|
end
|
109
|
-
|
110
|
-
},
|
89
|
+
end
|
111
90
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
src = relationship.source_optional? ? "odot" : "dot"
|
116
|
-
dst << "normal" if relationship.to_many?
|
117
|
-
src << "normal" if relationship.many_to?
|
118
|
-
options[:arrowsize] = 0.6
|
119
|
-
options[:arrowhead], options[:arrowtail] = dst, src
|
120
|
-
}
|
121
|
-
}
|
91
|
+
def relationship_style(relationship)
|
92
|
+
{}.tap do |options|
|
93
|
+
options[:style] = :dotted if relationship.indirect?
|
122
94
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
EDGE_ATTRIBUTES.each { |attribute, value| graph.edge[attribute] = value }
|
95
|
+
# Closed arrows for to/from many.
|
96
|
+
options[:arrowhead] = relationship.to_many? ? "normal" : "none"
|
97
|
+
options[:arrowtail] = relationship.many_to? ? "normal" : "none"
|
98
|
+
end
|
99
|
+
end
|
129
100
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
# Title of the graph itself.
|
134
|
-
graph[:label] = "#{title}\\n\\n" if title
|
101
|
+
def specialization_style(specialization)
|
102
|
+
{ :color => :grey60, :arrowtail => :onormal, :arrowhead => :none, :arrowsize => 1.2 }
|
135
103
|
end
|
136
104
|
end
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
raise "Saving diagram failed. Verify that Graphviz is installed or select filetype=dot."
|
144
|
-
end
|
105
|
+
|
106
|
+
module Bachman
|
107
|
+
include Simple
|
108
|
+
def relationship_style(relationship)
|
109
|
+
{}.tap do |options|
|
110
|
+
options[:style] = :dotted if relationship.indirect?
|
145
111
|
|
146
|
-
|
112
|
+
# Participation is "look-here".
|
113
|
+
dst = relationship.source_optional? ? "odot" : "dot"
|
114
|
+
src = relationship.destination_optional? ? "odot" : "dot"
|
147
115
|
|
148
|
-
|
149
|
-
|
116
|
+
# Cardinality is "look-across".
|
117
|
+
dst << "normal" if relationship.to_many?
|
118
|
+
src << "normal" if relationship.many_to?
|
119
|
+
options[:arrowsize] = 0.6
|
120
|
+
options[:arrowhead], options[:arrowtail] = dst, src
|
121
|
+
end
|
122
|
+
end
|
150
123
|
end
|
124
|
+
|
125
|
+
module Uml
|
126
|
+
include Simple
|
127
|
+
def relationship_style(relationship)
|
128
|
+
{}.tap do |options|
|
129
|
+
options[:style] = :dotted if relationship.indirect?
|
151
130
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
131
|
+
options[:arrowsize] = 0.7
|
132
|
+
options[:arrowhead] = relationship.to_many? ? "vee" : "none"
|
133
|
+
options[:arrowtail] = relationship.many_to? ? "vee" : "none"
|
156
134
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
135
|
+
ranges = [relationship.cardinality.destination_range, relationship.cardinality.source_range].map do |range|
|
136
|
+
if range.min == range.max
|
137
|
+
"#{range.min}"
|
138
|
+
else
|
139
|
+
"#{range.min}..#{range.max == Domain::Relationship::N ? "∗" : range.max}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
options[:headlabel], options[:taillabel] = *ranges
|
143
|
+
end
|
144
|
+
end
|
161
145
|
end
|
146
|
+
|
147
|
+
attr_accessor :graph
|
162
148
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
149
|
+
setup do
|
150
|
+
self.graph = GraphViz.digraph(domain.name)
|
151
|
+
|
152
|
+
# Set all default attributes.
|
153
|
+
GRAPH_ATTRIBUTES.each { |attribute, value| graph[attribute] = value }
|
154
|
+
NODE_ATTRIBUTES.each { |attribute, value| graph.node[attribute] = value }
|
155
|
+
EDGE_ATTRIBUTES.each { |attribute, value| graph.edge[attribute] = value }
|
156
|
+
|
157
|
+
# Switch rank direction if we're creating a vertically oriented graph.
|
158
|
+
graph[:rankdir] = :TB if options.orientation == :vertical
|
159
|
+
|
160
|
+
# Title of the graph itself.
|
161
|
+
graph[:label] = "#{title}\\n\\n" if title
|
162
|
+
|
163
|
+
# Setup notation options.
|
164
|
+
extend self.class.const_get(options.notation.to_s.capitalize.to_sym)
|
165
|
+
end
|
166
|
+
|
167
|
+
save do
|
168
|
+
raise "Saving diagram failed. Output directory '#{File.dirname(filename)}' does not exist." unless File.directory?(File.dirname(filename))
|
169
|
+
begin
|
170
|
+
graph.output(filetype => filename)
|
171
|
+
filename
|
172
|
+
rescue StandardError => e
|
173
|
+
raise "Saving diagram failed. Verify that Graphviz is installed or select filetype=dot."
|
174
|
+
end
|
167
175
|
end
|
168
176
|
|
177
|
+
each_entity do |entity, attributes|
|
178
|
+
draw_node entity.name, entity_options(entity, attributes)
|
179
|
+
end
|
180
|
+
|
181
|
+
each_specialization do |specialization|
|
182
|
+
from, to = specialization.generalized, specialization.specialized
|
183
|
+
draw_edge from.name, to.name, specialization_options(specialization)
|
184
|
+
end
|
185
|
+
|
186
|
+
each_relationship do |relationship|
|
187
|
+
from, to = relationship.source, relationship.destination
|
188
|
+
unless draw_edge from.name, to.name, relationship_options(relationship)
|
189
|
+
if from.children.any?
|
190
|
+
from.children.each do |child|
|
191
|
+
draw_edge child.name, to.name, relationship_options(relationship)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
169
197
|
private
|
170
198
|
|
199
|
+
def node_exists?(name)
|
200
|
+
!!graph.get_node(name)
|
201
|
+
end
|
202
|
+
|
203
|
+
def draw_node(name, options)
|
204
|
+
graph.add_node name, options
|
205
|
+
end
|
206
|
+
|
207
|
+
def draw_edge(from, to, options)
|
208
|
+
graph.add_edge graph.get_node(from), graph.get_node(to), options if node_exists?(from) and node_exists?(to)
|
209
|
+
end
|
210
|
+
|
171
211
|
# Returns the title to be used for the graph.
|
172
212
|
def title
|
173
213
|
case options.title
|
174
214
|
when false then nil
|
175
|
-
when true
|
176
|
-
if
|
215
|
+
when true
|
216
|
+
if domain.name then "#{domain.name} domain model" else "Domain model" end
|
177
217
|
else options.title
|
178
218
|
end
|
179
219
|
end
|
@@ -188,32 +228,23 @@ module RailsERD
|
|
188
228
|
if options.filetype.to_sym == :dot then :none else options.filetype.to_sym end
|
189
229
|
end
|
190
230
|
|
191
|
-
# Returns an options hash based on the given entity and its attributes.
|
192
231
|
def entity_options(entity, attributes)
|
193
|
-
|
232
|
+
entity_style(entity, attributes).merge :label => "<#{NODE_LABEL_TEMPLATE.result(binding)}>"
|
194
233
|
end
|
195
234
|
|
196
|
-
# Returns an options hash
|
197
235
|
def relationship_options(relationship)
|
198
|
-
|
236
|
+
relationship_style(relationship).tap do |options|
|
199
237
|
# Edges with a higher weight are optimised to be shorter and straighter.
|
200
|
-
|
238
|
+
options[:weight] = relationship.strength
|
201
239
|
|
202
240
|
# Indirect relationships should not influence node ranks.
|
203
|
-
|
241
|
+
options[:constraint] = false if relationship.indirect?
|
204
242
|
end
|
205
243
|
end
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
def relationship_style_options(relationship)
|
210
|
-
{}.tap do |opts|
|
211
|
-
opts[:style] = :dotted if relationship.indirect?
|
212
|
-
|
213
|
-
# Let cardinality style callbacks draw arrow heads and tails.
|
214
|
-
CARDINALITY_STYLES[options.notation][relationship, opts]
|
215
|
-
end
|
244
|
+
|
245
|
+
def specialization_options(specialization)
|
246
|
+
specialization_style(specialization)
|
216
247
|
end
|
217
248
|
end
|
218
249
|
end
|
219
|
-
end
|
250
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<% if vertical
|
1
|
+
<% if options.orientation == :vertical %>{<% end %>
|
2
2
|
<table border="0" align="center" cellspacing="0.5" cellpadding="0" width="<%= NODE_WIDTH + 4 %>">
|
3
3
|
<tr><td align="center" valign="bottom" width="<%= NODE_WIDTH %>"><font face="Arial Bold" point-size="11"><%= entity.name %></font></td></tr>
|
4
4
|
</table>
|
@@ -11,4 +11,4 @@
|
|
11
11
|
</table>
|
12
12
|
<% else %>
|
13
13
|
<% end %>
|
14
|
-
<% if vertical
|
14
|
+
<% if options.orientation == :vertical %>}<% end %>
|
data/lib/rails_erd/domain.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require "set"
|
2
1
|
require "rails_erd"
|
3
|
-
require "rails_erd/
|
4
|
-
require "rails_erd/
|
5
|
-
require "rails_erd/
|
2
|
+
require "rails_erd/domain/attribute"
|
3
|
+
require "rails_erd/domain/entity"
|
4
|
+
require "rails_erd/domain/relationship"
|
5
|
+
require "rails_erd/domain/specialization"
|
6
6
|
|
7
7
|
module RailsERD
|
8
8
|
# The domain describes your Rails domain model. This class is the starting
|
@@ -28,13 +28,16 @@ module RailsERD
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
extend Inspectable
|
32
|
+
inspection_attributes
|
33
|
+
|
31
34
|
# The options that are used to generate this domain model.
|
32
35
|
attr_reader :options
|
33
36
|
|
34
37
|
# Create a new domain model object based on the given array of models.
|
35
38
|
# The given models are assumed to be subclasses of <tt>ActiveRecord::Base</tt>.
|
36
39
|
def initialize(models = [], options = {})
|
37
|
-
@
|
40
|
+
@source_models, @options = models, RailsERD.options.merge(options)
|
38
41
|
end
|
39
42
|
|
40
43
|
# Returns the domain model name, which is the name of your Rails
|
@@ -45,7 +48,7 @@ module RailsERD
|
|
45
48
|
|
46
49
|
# Returns all entities of your domain model.
|
47
50
|
def entities
|
48
|
-
@entities ||= Entity.from_models(self,
|
51
|
+
@entities ||= Entity.from_models(self, models)
|
49
52
|
end
|
50
53
|
|
51
54
|
# Returns all relationships in your domain model.
|
@@ -53,19 +56,23 @@ module RailsERD
|
|
53
56
|
@relationships ||= Relationship.from_associations(self, associations)
|
54
57
|
end
|
55
58
|
|
59
|
+
# Returns all specializations in your domain model.
|
60
|
+
def specializations
|
61
|
+
@specializations ||= Specialization.from_models(self, models)
|
62
|
+
end
|
63
|
+
|
56
64
|
# Returns a specific entity object for the given Active Record model.
|
57
|
-
def
|
58
|
-
entity_mapping[
|
65
|
+
def entity_by_name(name) # @private :nodoc:
|
66
|
+
entity_mapping[name]
|
59
67
|
end
|
60
68
|
|
61
69
|
# Returns an array of relationships for the given Active Record model.
|
62
|
-
def
|
63
|
-
relationships_mapping[
|
70
|
+
def relationships_by_entity_name(name) # @private :nodoc:
|
71
|
+
relationships_mapping[name] or []
|
64
72
|
end
|
65
|
-
|
66
|
-
def
|
67
|
-
|
68
|
-
[object_id << 1, relationships.map { |rel| "#{rel.source} => #{rel.destination}" } * ", "]
|
73
|
+
|
74
|
+
def specializations_by_entity_name(name)
|
75
|
+
specializations_mapping[name] or []
|
69
76
|
end
|
70
77
|
|
71
78
|
def warn(message) # @private :nodoc:
|
@@ -77,7 +84,7 @@ module RailsERD
|
|
77
84
|
def entity_mapping
|
78
85
|
@entity_mapping ||= {}.tap do |mapping|
|
79
86
|
entities.each do |entity|
|
80
|
-
mapping[entity.
|
87
|
+
mapping[entity.name] = entity
|
81
88
|
end
|
82
89
|
end
|
83
90
|
end
|
@@ -85,25 +92,46 @@ module RailsERD
|
|
85
92
|
def relationships_mapping
|
86
93
|
@relationships_mapping ||= {}.tap do |mapping|
|
87
94
|
relationships.each do |relationship|
|
88
|
-
(mapping[relationship.source.
|
89
|
-
(mapping[relationship.destination.
|
95
|
+
(mapping[relationship.source.name] ||= []) << relationship
|
96
|
+
(mapping[relationship.destination.name] ||= []) << relationship
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def specializations_mapping
|
102
|
+
@specializations_mapping ||= {}.tap do |mapping|
|
103
|
+
specializations.each do |specialization|
|
104
|
+
(mapping[specialization.generalized.name] ||= []) << specialization
|
105
|
+
(mapping[specialization.specialized.name] ||= []) << specialization
|
90
106
|
end
|
91
107
|
end
|
92
108
|
end
|
93
109
|
|
110
|
+
def models
|
111
|
+
@models ||= @source_models.reject(&:abstract_class?).select { |model| check_model_validity(model) }
|
112
|
+
end
|
113
|
+
|
94
114
|
def associations
|
95
|
-
@associations ||=
|
115
|
+
@associations ||= models.collect(&:reflect_on_all_associations).flatten.select { |assoc| check_association_validity(assoc) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def check_model_validity(model)
|
119
|
+
model.table_exists? or raise "table #{model.table_name} does not exist"
|
120
|
+
rescue => e
|
121
|
+
warn "Ignoring invalid model #{model.name} (#{e.message})"
|
96
122
|
end
|
97
123
|
|
98
124
|
def check_association_validity(association)
|
99
125
|
# Raises an ActiveRecord::ActiveRecordError if the association is broken.
|
100
126
|
association.check_validity!
|
101
127
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
128
|
+
if association.options[:polymorphic]
|
129
|
+
entity_name = association.class_name
|
130
|
+
entity_by_name(entity_name) or raise "polymorphic interface #{entity_name} does not exist"
|
131
|
+
else
|
132
|
+
entity_name = association.klass.name # Raises NameError if the associated class cannot be found.
|
133
|
+
entity_by_name(entity_name) or raise "model #{entity_name} exists, but is not included in domain"
|
134
|
+
end
|
107
135
|
rescue => e
|
108
136
|
warn "Ignoring invalid association #{association_description(association)} (#{e.message})"
|
109
137
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module RailsERD
|
3
|
+
class Domain
|
4
|
+
# Describes an entity's attribute. Attributes correspond directly to
|
5
|
+
# database columns.
|
6
|
+
class Attribute
|
7
|
+
TIMESTAMP_NAMES = %w{created_at created_on updated_at updated_on} # @private :nodoc:
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def from_model(domain, model) # @private :nodoc:
|
11
|
+
model.columns.collect { |column| new(domain, model, column) }.sort
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
extend Inspectable
|
16
|
+
inspection_attributes :name, :type
|
17
|
+
|
18
|
+
attr_reader :column # @private :nodoc:
|
19
|
+
|
20
|
+
def initialize(domain, model, column) # @private :nodoc:
|
21
|
+
@domain, @model, @column = domain, model, column
|
22
|
+
end
|
23
|
+
|
24
|
+
# The name of the attribute, equal to the column name.
|
25
|
+
def name
|
26
|
+
column.name
|
27
|
+
end
|
28
|
+
|
29
|
+
# The type of the attribute, equal to the Rails migration type. Can be any
|
30
|
+
# of +:string+, +:integer+, +:boolean+, +:text+, etc.
|
31
|
+
def type
|
32
|
+
column.type
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns +true+ if this attribute is a content column, that is, if it
|
36
|
+
# is not a primary key, foreign key, timestamp, or inheritance column.
|
37
|
+
def content?
|
38
|
+
!primary_key? and !foreign_key? and !timestamp? and !inheritance?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns +true+ if this attribute is mandatory. Mandatory attributes
|
42
|
+
# either have a presence validation (+validates_presence_of+), or have a
|
43
|
+
# <tt>NOT NULL</tt> database constraint.
|
44
|
+
def mandatory?
|
45
|
+
!column.null or @model.validators_on(name).map(&:kind).include?(:presence)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns +true+ if this attribute is the primary key of the entity.
|
49
|
+
def primary_key?
|
50
|
+
column.primary
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns +true+ if this attribute is used as a foreign key for any
|
54
|
+
# relationship.
|
55
|
+
def foreign_key?
|
56
|
+
@domain.relationships_by_entity_name(@model.name).map(&:associations).flatten.map(&:primary_key_name).include?(name)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns +true+ if this attribute is used for single table inheritance.
|
60
|
+
# These attributes are typically named +type+.
|
61
|
+
def inheritance?
|
62
|
+
@model.inheritance_column == name
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns +true+ if this attribute is one of the standard 'magic' Rails
|
66
|
+
# timestamp columns, being +created_at+, +updated_at+, +created_on+ or
|
67
|
+
# +updated_on+.
|
68
|
+
def timestamp?
|
69
|
+
TIMESTAMP_NAMES.include? name
|
70
|
+
end
|
71
|
+
|
72
|
+
def <=>(other) # @private :nodoc:
|
73
|
+
name <=> other.name
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s # @private :nodoc:
|
77
|
+
name
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a description of the attribute type. If the attribute has
|
81
|
+
# a non-standard limit or if it is mandatory, this information is included.
|
82
|
+
#
|
83
|
+
# Example output:
|
84
|
+
# <tt>:integer</tt>:: integer
|
85
|
+
# <tt>:string, :limit => 255</tt>:: string
|
86
|
+
# <tt>:string, :limit => 128</tt>:: string (128)
|
87
|
+
# <tt>:boolean, :null => false</tt>:: boolean *
|
88
|
+
def type_description
|
89
|
+
type.to_s.tap do |desc|
|
90
|
+
desc << " (#{limit})" if limit
|
91
|
+
desc << " ∗" if mandatory? # Add a hair space + low asterisk (Unicode characters).
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns any non-standard limit for this attribute. If a column has no
|
96
|
+
# limit or uses a default database limit, this method returns +nil+.
|
97
|
+
def limit
|
98
|
+
column.limit if column.limit != @model.connection.native_database_types[type][:limit]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|