activefacts 0.8.13 → 0.8.15
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.
- 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
|