activefacts 0.8.13 → 0.8.15
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +6 -0
- data/Rakefile +68 -41
- data/TODO +308 -0
- data/lib/activefacts/cql/FactTypes.treetop +1 -1
- data/lib/activefacts/generate/dm.rb +273 -0
- data/lib/activefacts/generate/help.rb +45 -0
- data/lib/activefacts/generate/html/glossary.rb +209 -0
- data/lib/activefacts/generate/json.rb +334 -0
- data/lib/activefacts/generate/records.rb +45 -0
- data/lib/activefacts/generate/version.rb +25 -0
- data/lib/activefacts/version.rb +8 -1
- metadata +220 -36
- data/.gemtest +0 -0
- data/examples/CQL/MetamodelNext.cql +0 -477
@@ -264,7 +264,7 @@ module ActiveFacts
|
|
264
264
|
{
|
265
265
|
def ast
|
266
266
|
raise "Not implemented: AST for '#{aggregate.text_value} of #{term.text_value}'"
|
267
|
-
# This returns just the role with the nested clauses, which doesn
|
267
|
+
# This returns just the role with the nested clauses, which doesn't even work:
|
268
268
|
term.ast(
|
269
269
|
nil, # No quantifier
|
270
270
|
nil, # No function call
|
@@ -0,0 +1,273 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generate Ruby code for Data Mapper from an ActiveFacts vocabulary.
|
4
|
+
#
|
5
|
+
# A testing strategy:
|
6
|
+
# Generate and load a set of models
|
7
|
+
# call DataMapper::finalize to check they're consistent
|
8
|
+
# call DataMapper::Spec.cleanup_models to delete them again
|
9
|
+
#
|
10
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
11
|
+
#
|
12
|
+
require 'activefacts/vocabulary'
|
13
|
+
require 'activefacts/persistence'
|
14
|
+
require 'activefacts/generate/oo'
|
15
|
+
|
16
|
+
module ActiveFacts
|
17
|
+
module Generate
|
18
|
+
class DM < OO #:nodoc:
|
19
|
+
# Generate SQL for DataMapper for an ActiveFacts vocabulary.
|
20
|
+
# Invoke as
|
21
|
+
# afgen --dm[=options] <file>.cql
|
22
|
+
# Options:
|
23
|
+
# dir=<mixins directory>
|
24
|
+
# Example:
|
25
|
+
# afgen --dm=dir=app/mixins MyApp.cql
|
26
|
+
include Persistence
|
27
|
+
|
28
|
+
def initialize(vocabulary, *options)
|
29
|
+
@vocabulary = vocabulary
|
30
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
31
|
+
@mixins = options.grep(/^dir=/)[-1]
|
32
|
+
@mixins && @mixins.sub!(/^dir=/,'')
|
33
|
+
end
|
34
|
+
|
35
|
+
def puts s
|
36
|
+
@out.puts s
|
37
|
+
end
|
38
|
+
|
39
|
+
def model_file(name)
|
40
|
+
@mixins+'/'+name.gsub(/\s/,'')+'.rb'
|
41
|
+
end
|
42
|
+
|
43
|
+
def class_name(name)
|
44
|
+
name.gsub(/\s/,'')
|
45
|
+
end
|
46
|
+
|
47
|
+
def column_name(column)
|
48
|
+
column.name('_').snakecase
|
49
|
+
end
|
50
|
+
|
51
|
+
def symbol_name(name)
|
52
|
+
name.gsub(/\s/,'_').snakecase
|
53
|
+
end
|
54
|
+
|
55
|
+
def new_output(name)
|
56
|
+
return unless @mixins
|
57
|
+
@out.flush
|
58
|
+
@out = File.open(model_file(name), "w")
|
59
|
+
puts "require 'datamapper'\n\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
def key_fields(ref, reverse = false)
|
63
|
+
# Compute and return child_key and parent_key if necessary
|
64
|
+
fk = ref.from.foreign_keys.detect{|k| k.reference == ref}
|
65
|
+
child_key = fk.from_columns.map{|c| column_name(c)}
|
66
|
+
parent_key = fk.to_columns.map{|c| column_name(c)}
|
67
|
+
if child_key != parent_key
|
68
|
+
c, p = *(reverse ? ['parent', 'child'] : ['child', 'parent'])
|
69
|
+
", :#{c}_key => [:#{child_key*', :'}], :#{p}_key => [:#{parent_key*', :'}]"
|
70
|
+
else
|
71
|
+
''
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
public
|
76
|
+
|
77
|
+
def generate(out = $>) #:nodoc:
|
78
|
+
@out = out
|
79
|
+
|
80
|
+
# Calculate the relational absorption:
|
81
|
+
tables = @vocabulary.tables
|
82
|
+
|
83
|
+
# Figure out which ObjectType will be models (tables and their subtypes)
|
84
|
+
models =
|
85
|
+
@vocabulary.all_object_type.sort_by{|o| o.name}.select do |o|
|
86
|
+
next false if o.name =~ /_?ImplicitBooleanValueType/
|
87
|
+
o.is_table || (o.absorbed_via && o.absorbed_via.role_type == :supertype)
|
88
|
+
end
|
89
|
+
is_model = models.inject({}) { |h, m| h[m] = true; h }
|
90
|
+
|
91
|
+
puts "require 'dm-core'"
|
92
|
+
puts "require 'dm-constraints'"
|
93
|
+
puts "\n"
|
94
|
+
|
95
|
+
# Dump tables until all done, subtypes before supertypes:
|
96
|
+
until models.empty?
|
97
|
+
# Choose another object type that we can dump now:
|
98
|
+
o = models.detect do |o|
|
99
|
+
next true if o.is_table
|
100
|
+
next true if a = o.absorbed_via and a.role_type == :supertype and supertype = a.from and !models.include?(supertype)
|
101
|
+
false
|
102
|
+
end
|
103
|
+
models.delete(o)
|
104
|
+
|
105
|
+
supertype = (a = o.absorbed_via and a.role_type == :supertype) ? supertype = a.from : nil
|
106
|
+
if o.is_a?(ActiveFacts::Metamodel::EntityType)
|
107
|
+
if secondary_supertypes = o.supertypes-[supertype] and
|
108
|
+
secondary_supertypes.size > 0 and
|
109
|
+
secondary_supertypes.detect do |sst|
|
110
|
+
sst_ref_facts = sst.preferred_identifier.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
|
111
|
+
non_identifying_inheritable_references =
|
112
|
+
sst.references_from.reject do |ref|
|
113
|
+
sst_ref_facts.include?(ref.fact_type)
|
114
|
+
end
|
115
|
+
non_identifying_inheritable_references.size > 0
|
116
|
+
end
|
117
|
+
raise "Cannot map classes like #{o.name} with roles inherited from external supertypes (#{secondary_supertypes.map{|t|t.name}*", "})"
|
118
|
+
end
|
119
|
+
pi = o.preferred_identifier
|
120
|
+
identifying_role_refs = pi.role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
121
|
+
identifying_facts = ([o.fact_type]+identifying_role_refs.map{|rr| rr.role.fact_type }).compact.uniq
|
122
|
+
else
|
123
|
+
identifying_facts = []
|
124
|
+
end
|
125
|
+
|
126
|
+
# REVISIT: STI fails where the base class is absorbed into another table, like Incident in Insurance for example.
|
127
|
+
# In this case you get the subtype fields absorbed and should not get an STI model.
|
128
|
+
|
129
|
+
puts "class #{class_name(o.name)}#{supertype ? " < #{class_name(supertype.name)}" : ''}"
|
130
|
+
puts " include DataMapper::Resource\n\n" unless supertype
|
131
|
+
|
132
|
+
columns = o.columns
|
133
|
+
o.references_from.each do |ref|
|
134
|
+
# A (set of) columns
|
135
|
+
if !columns
|
136
|
+
# absorbed subtypes didn't have columns populated
|
137
|
+
columns = o.all_columns({})
|
138
|
+
end
|
139
|
+
|
140
|
+
next if [:subtype, :supertype].include?(ref.role_type)
|
141
|
+
# debugger if ref_columns.detect{|c| [:subtype, :supertype].include?(c.references[0].role_type)}
|
142
|
+
ref_columns = columns.select{|c| c.references[0] == ref }
|
143
|
+
# puts " \# #{ref.reading}:"
|
144
|
+
ref_columns.each do |column|
|
145
|
+
type, params, constraints = column.type
|
146
|
+
length = params[:length]
|
147
|
+
length &&= length.to_i
|
148
|
+
scale = params[:scale]
|
149
|
+
scale &&= scale.to_i
|
150
|
+
type, length = normalise_type(type, length)
|
151
|
+
key = identifying_facts.include?(column.references[0].fact_type) ||
|
152
|
+
(identifying_facts.empty? && ref.is_self_value)
|
153
|
+
cname = column_name(column)
|
154
|
+
required = column.is_mandatory && !key ? ", :required => true" : "" # Key fields are implicitly required
|
155
|
+
if type == 'Serial'
|
156
|
+
if !key || o.preferred_identifier.role_sequence.all_role_ref.size != 1
|
157
|
+
type = 'Integer'
|
158
|
+
else
|
159
|
+
key = false # This is implicit
|
160
|
+
end
|
161
|
+
end
|
162
|
+
$stderr.puts "Warning: non-mandatory key field #{o.name}.#{column.name} is forced to mandatory" if !column.is_mandatory && key
|
163
|
+
puts " property :#{column_name(column)}, #{type}#{length ? ", :length => "+length.to_s : ''}#{required}#{key ? ', :key => true' : ''}\t\# #{column.comment}"
|
164
|
+
end
|
165
|
+
|
166
|
+
if is_model[ref.to]
|
167
|
+
# An association
|
168
|
+
reverse = false
|
169
|
+
association_type =
|
170
|
+
case ref.role_type
|
171
|
+
when :one_one
|
172
|
+
reverse = true
|
173
|
+
"has 1,"
|
174
|
+
when :one_many, :many_one
|
175
|
+
"belongs_to"
|
176
|
+
when :supertype
|
177
|
+
next
|
178
|
+
when :subtype
|
179
|
+
next
|
180
|
+
else
|
181
|
+
raise "role type #{ref.role_type} not handled"
|
182
|
+
end
|
183
|
+
|
184
|
+
association_name = (ref.to_names*'_')
|
185
|
+
model_name = association_name != ref.to.name ? model_name = ", '#{class_name(ref.to.name)}'" : ''
|
186
|
+
comment = o.fact_type ? "#{association_name} is involved in #{o.name}" : ref.reading
|
187
|
+
keys = key_fields(ref, reverse)
|
188
|
+
puts " #{association_type} :#{association_name.downcase}#{model_name}#{keys}\t\# #{comment}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Emit the "has n," associations
|
193
|
+
# REVISIT: Need to use ActiveSupport to pluralise these names, or disable inflexion somehow.
|
194
|
+
o.references_to.each do |ref|
|
195
|
+
next unless is_model[ref.from]
|
196
|
+
constraint = ''
|
197
|
+
association_type =
|
198
|
+
case ref.role_type
|
199
|
+
when :one_one
|
200
|
+
"has 1,"
|
201
|
+
when :many_one, :one_many
|
202
|
+
constraint = ', :constraint => :destroy' # REVISIT: Check mandatory, and use nullify?
|
203
|
+
"has n,"
|
204
|
+
else
|
205
|
+
next
|
206
|
+
end
|
207
|
+
prr = ref.fact_type.preferred_reading.role_sequence.all_role_ref.detect{|rr| rr.role == ref.to_role}
|
208
|
+
association_name = (ref.from_names*'_')
|
209
|
+
if prr && (prr.role.role_name || prr.leading_adjective || prr.trailing_adjective)
|
210
|
+
association_name += "_as_"+symbol_name(ref.to_names*'_')
|
211
|
+
end
|
212
|
+
model_name = association_name != ref.from.name ? model_name = ", '#{class_name(ref.from.name)}'" : ''
|
213
|
+
comment = o.is_a?(ActiveFacts::Metamodel::EntityType) && o.fact_type ? "#{association_name} is involved in #{o.name}" : ref.reading
|
214
|
+
keys = key_fields(ref)
|
215
|
+
|
216
|
+
puts " #{association_type} :#{association_name.downcase}#{model_name}#{keys}\t\# #{comment}"
|
217
|
+
end
|
218
|
+
puts "end\n\n"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
# Return DataMapper type and (modified?) length for the passed base type
|
224
|
+
def normalise_type(type, length)
|
225
|
+
dm_type = case type
|
226
|
+
when /^Auto ?Counter$/
|
227
|
+
'Serial'
|
228
|
+
|
229
|
+
when /^Unsigned ?Integer$/,
|
230
|
+
/^Signed ?Integer$/,
|
231
|
+
/^Unsigned ?Small ?Integer$/,
|
232
|
+
/^Signed ?Small ?Integer$/,
|
233
|
+
/^Unsigned ?Tiny ?Integer$/
|
234
|
+
length = nil
|
235
|
+
'Integer'
|
236
|
+
|
237
|
+
when /^Decimal$/
|
238
|
+
'Decimal'
|
239
|
+
|
240
|
+
when /^Fixed ?Length ?Text$/, /^Char$/
|
241
|
+
'String'
|
242
|
+
when /^Variable ?Length ?Text$/, /^String$/
|
243
|
+
'String'
|
244
|
+
when /^Large ?Length ?Text$/, /^Text$/
|
245
|
+
'Text'
|
246
|
+
|
247
|
+
when /^Date ?And ?Time$/, /^Date ?Time$/
|
248
|
+
'DateTime'
|
249
|
+
when /^Date$/
|
250
|
+
'DateTime'
|
251
|
+
when /^Time$/
|
252
|
+
'DateTime'
|
253
|
+
when /^Auto ?Time ?Stamp$/
|
254
|
+
'DateTime'
|
255
|
+
|
256
|
+
when /^Money$/
|
257
|
+
'Decimal'
|
258
|
+
when /^Picture ?Raw ?Data$/, /^Image$/
|
259
|
+
'String'
|
260
|
+
when /^Variable ?Length ?Raw ?Data$/, /^Blob$/
|
261
|
+
'String'
|
262
|
+
when /^BIT$/
|
263
|
+
'Boolean'
|
264
|
+
else
|
265
|
+
# raise "DataMapper type unknown for standard type #{type}"
|
266
|
+
type
|
267
|
+
end
|
268
|
+
[dm_type, length]
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Provides help for afgen - from afgen --help
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
require 'activefacts/persistence'
|
8
|
+
|
9
|
+
module ActiveFacts
|
10
|
+
module Generate
|
11
|
+
# Generate nothing from an ActiveFacts vocabulary. This is useful to check the file can be read ok.
|
12
|
+
# Invoke as
|
13
|
+
# afgen --null <file>.cql
|
14
|
+
class HELP
|
15
|
+
private
|
16
|
+
def initialize(vocabulary, *options)
|
17
|
+
puts %Q{
|
18
|
+
Usage: afgen --generator[=options] file.inp[=options]
|
19
|
+
options are comma-separated lists. Use =help to get more information.
|
20
|
+
|
21
|
+
Available generators are:
|
22
|
+
#{$:.map{|path|
|
23
|
+
Dir[path+"/activefacts/generate/**.rb"].map{|p|
|
24
|
+
p.sub(%r{.*/}, '').sub(/\.rb/,'')
|
25
|
+
}
|
26
|
+
}.flatten.uniq.sort.join("\n\t")
|
27
|
+
}
|
28
|
+
|
29
|
+
inp is the name of a file input handler. Available input handlers are:
|
30
|
+
#{$:.map{|path|
|
31
|
+
Dir[path+"/activefacts/input/**.rb"].map{|p|
|
32
|
+
p.sub(%r{.*/}, '').sub(/\.rb/,'')
|
33
|
+
}
|
34
|
+
}.flatten.uniq.sort.join("\n\t")
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
public
|
40
|
+
def generate(out = $>)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,209 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
#
|
4
|
+
# Generate a glossary in HTML
|
5
|
+
#
|
6
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
7
|
+
#
|
8
|
+
require 'activefacts/api'
|
9
|
+
|
10
|
+
module ActiveFacts
|
11
|
+
module Generate #:nodoc:
|
12
|
+
class HTML #:nodoc:
|
13
|
+
class GLOSSARY #:nodoc:
|
14
|
+
# Base class for generators of object-oriented class libraries for an ActiveFacts vocabulary.
|
15
|
+
def initialize(vocabulary, *options)
|
16
|
+
@vocabulary = vocabulary
|
17
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
18
|
+
options.each{|option| set_option(option) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_option(option)
|
22
|
+
end
|
23
|
+
|
24
|
+
def puts(*a)
|
25
|
+
@out.puts *a
|
26
|
+
end
|
27
|
+
|
28
|
+
def print(*a)
|
29
|
+
@out.print *a
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate(out = $>)
|
33
|
+
@out = out
|
34
|
+
vocabulary_start
|
35
|
+
|
36
|
+
object_types_dump()
|
37
|
+
|
38
|
+
vocabulary_end
|
39
|
+
end
|
40
|
+
|
41
|
+
def vocabulary_start
|
42
|
+
puts "<link rel='stylesheet' href='css/orm2.css' media='screen' type='text/css'/>"
|
43
|
+
puts "<h1>#{@vocabulary.name}</h1>"
|
44
|
+
puts "<dl>"
|
45
|
+
end
|
46
|
+
|
47
|
+
def vocabulary_end
|
48
|
+
puts "</dl>"
|
49
|
+
end
|
50
|
+
|
51
|
+
def object_types_dump
|
52
|
+
@vocabulary.
|
53
|
+
all_object_type.
|
54
|
+
sort_by{|o| o.name.gsub(/ /,'').downcase}.
|
55
|
+
each do |o|
|
56
|
+
case o
|
57
|
+
when ActiveFacts::Metamodel::TypeInheritance
|
58
|
+
nil
|
59
|
+
when ActiveFacts::Metamodel::ValueType
|
60
|
+
value_type_dump(o)
|
61
|
+
else
|
62
|
+
if o.fact_type
|
63
|
+
objectified_fact_type_dump(o)
|
64
|
+
else
|
65
|
+
entity_type_dump(o)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def element(text, attrs, tag = 'span')
|
72
|
+
"<#{tag}#{attrs.empty? ? '' : attrs.map{|k,v| " #{k}='#{v}'"}*''}>#{text}</#{tag}>"
|
73
|
+
end
|
74
|
+
|
75
|
+
# A definition of a term
|
76
|
+
def termdef(name)
|
77
|
+
element(name, {:name => name, :class => 'object_type'}, 'a')
|
78
|
+
end
|
79
|
+
|
80
|
+
# A reference to a defined term (excluding role adjectives)
|
81
|
+
def termref(name, role_name = nil)
|
82
|
+
role_name ||= name
|
83
|
+
element(role_name, {:href=>'#'+name, :class=>:object_type}, 'a')
|
84
|
+
end
|
85
|
+
|
86
|
+
# Text that should appear as part of a term (including role adjectives)
|
87
|
+
def term(name)
|
88
|
+
element(name, :class=>:object_type)
|
89
|
+
end
|
90
|
+
|
91
|
+
def value_type_dump(o)
|
92
|
+
return if o.all_role.size == 0 # Skip value types that are only used as supertypes
|
93
|
+
puts " <dt>" +
|
94
|
+
"Value Type: #{termdef(o.name)}" +
|
95
|
+
(o.supertype ? " (written as #{termref(o.supertype.name)})" : "") +
|
96
|
+
"</dt>"
|
97
|
+
|
98
|
+
puts " <dd>"
|
99
|
+
relevant_facts_and_constraints(o)
|
100
|
+
puts " </dd>"
|
101
|
+
end
|
102
|
+
|
103
|
+
def relevant_facts_and_constraints(o)
|
104
|
+
puts(
|
105
|
+
o.
|
106
|
+
all_role.
|
107
|
+
map{|r| r.fact_type}.
|
108
|
+
uniq.
|
109
|
+
reject{|ft| ft.is_a?(ActiveFacts::Metamodel::TypeInheritance) || ft.is_a?(ActiveFacts::Metamodel::ImplicitFactType) }.
|
110
|
+
map { |ft| " #{fact_type_with_constraints(ft, o)}</br>" }.
|
111
|
+
sort * "\n"
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
def role_ref rr, freq_con, l_adj, name, t_adj, role_name_def, literal
|
116
|
+
term_parts = [l_adj, termref(name), t_adj].compact
|
117
|
+
[
|
118
|
+
freq_con ? element(freq_con, :class=>:keyword) : nil,
|
119
|
+
term_parts.size > 1 ? term([l_adj, termref(name), t_adj].compact*' ') : term_parts[0],
|
120
|
+
role_name_def,
|
121
|
+
literal
|
122
|
+
]
|
123
|
+
end
|
124
|
+
|
125
|
+
def expand_reading(r)
|
126
|
+
element(
|
127
|
+
r.expand do |*a|
|
128
|
+
role_ref(*a)
|
129
|
+
end,
|
130
|
+
{:class => 'copula'}
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
def fact_type(ft, wrt = nil)
|
135
|
+
role = ft.all_role.detect{|r| r.object_type == wrt}
|
136
|
+
preferred_reading = ft.reading_preferably_starting_with_role(role)
|
137
|
+
alternate_readings = ft.all_reading.reject{|r| r == preferred_reading}
|
138
|
+
expand_reading(preferred_reading) +
|
139
|
+
(alternate_readings.size > 0 ?
|
140
|
+
' (alternatively, ' +
|
141
|
+
alternate_readings.map { |r| expand_reading(r)}*', ' +
|
142
|
+
')' : '')
|
143
|
+
end
|
144
|
+
|
145
|
+
def fact_type_with_constraints(ft, wrt = nil)
|
146
|
+
fact_type(ft, wrt) +
|
147
|
+
"<br/>\n<ul>\n" +
|
148
|
+
fact_type_constraints(ft) +
|
149
|
+
"</ul>"
|
150
|
+
end
|
151
|
+
|
152
|
+
def fact_type_constraints(ft)
|
153
|
+
ft.internal_presence_constraints.map do |pc|
|
154
|
+
residual_role = ft.all_role.detect{|r| !pc.role_sequence.all_role_ref.detect{|rr| rr.role == r}}
|
155
|
+
next nil unless residual_role
|
156
|
+
reading = ft.all_reading.detect{|reading|
|
157
|
+
reading.role_sequence.all_role_ref_in_order[reading.role_numbers[-1]].role == residual_role
|
158
|
+
}
|
159
|
+
next nil unless reading
|
160
|
+
element(
|
161
|
+
reading.expand_with_final_presence_constraint { |*a| role_ref(*a) },
|
162
|
+
{:class => 'copula'}
|
163
|
+
)+"<br/>\n"
|
164
|
+
end.compact*''
|
165
|
+
end
|
166
|
+
|
167
|
+
def objectified_fact_type_dump(o)
|
168
|
+
puts " <dt>" +
|
169
|
+
"Entity Type: #{termdef(o.name)}" +
|
170
|
+
" (objectification of #{fact_type(o.fact_type)})" +
|
171
|
+
"</dt>"
|
172
|
+
# REVISIT: Handle separate identification
|
173
|
+
|
174
|
+
puts " <dd>"
|
175
|
+
puts fact_type_constraints(o.fact_type)
|
176
|
+
o.fact_type.all_role_in_order.each do |r|
|
177
|
+
n = r.object_type.name
|
178
|
+
puts "#{termref(o.name)} involves exactly one #{termref(r.role_name || n, n)}<br/>"
|
179
|
+
end
|
180
|
+
relevant_facts_and_constraints(o)
|
181
|
+
puts " </dd>"
|
182
|
+
end
|
183
|
+
|
184
|
+
def entity_type_dump(o)
|
185
|
+
pi = o.preferred_identifier
|
186
|
+
supers = o.supertypes
|
187
|
+
if (supers.size > 0) # Ignore identification by a supertype:
|
188
|
+
pi = nil if pi && pi.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) }
|
189
|
+
end
|
190
|
+
|
191
|
+
puts " <dt>" +
|
192
|
+
"Entity Type: #{termdef(o.name)}" +
|
193
|
+
" (" +
|
194
|
+
[
|
195
|
+
(supers.size > 0 ? "Subtype of #{supers.map{|s| s.name}*', '})" : nil),
|
196
|
+
(pi ? "identified by "+pi.role_sequence.describe : nil)
|
197
|
+
].compact*', '
|
198
|
+
")" +
|
199
|
+
"</dt>"
|
200
|
+
|
201
|
+
puts " <dd>"
|
202
|
+
relevant_facts_and_constraints(o)
|
203
|
+
puts " </dd>"
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|