activefacts 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -188,7 +188,7 @@ module ActiveFacts
188
188
 
189
189
  # If we emitted a reading for the refmode, it'll include any role_value_constraint already
190
190
  if nonstandard_readings.size == 0 and c = value_role.role_value_constraint
191
- constraint_text = " "+c.describe
191
+ constraint_text = " "+c.as_cql
192
192
  end
193
193
  (entity_type.is_independent ? ' independent' : '') +
194
194
  " identified by its #{value_residual}#{constraint_text}#{mapping_pragma(entity_type, true)}" +
@@ -651,6 +651,8 @@ module ActiveFacts
651
651
  parameters
652
652
  }#{
653
653
  unit && " "+unit.name
654
+ }#{
655
+ transaction_phase && " auto-assigned at "+transaction_phase
654
656
  }#{
655
657
  concept.all_context_note_as_relevant_concept.map do |cn|
656
658
  cn.verbalise
@@ -0,0 +1,16 @@
1
+ require 'activefacts/vocabulary'
2
+
3
+ module ActiveFacts
4
+ module TraitInjector
5
+ def self.included other
6
+ overlap = Metamodel.constants & other.constants
7
+ overlap.each do |const|
8
+ mix_into = Metamodel.const_get(const)
9
+ mix_in = other.const_get(const)
10
+ mix_into.instance_exec {
11
+ include(mix_in)
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -151,7 +151,7 @@ module ActiveFacts
151
151
 
152
152
  ref = fk.jump_reference
153
153
  [
154
- "\n \# #{fk.verbalised_path}" +
154
+ "\n \# #{fk.verbalised_path(true)}" +
155
155
  "\n" +
156
156
  %Q{ #{association_type} :#{association_name}} +
157
157
  %Q{, :class_name => '#{fk.from.rails_class_name}'} +
@@ -0,0 +1,69 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Generate metamodel statistics fora compiled vocabulary
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 a text verbalisation of the metamodel constellation created for an ActiveFacts vocabulary.
12
+ # Invoke as
13
+ # afgen --text <file>.cql
14
+ class Statistics
15
+ private
16
+ def initialize(vocabulary)
17
+ @vocabulary = vocabulary
18
+ @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
19
+ end
20
+
21
+ public
22
+ def generate(out = $>)
23
+ constellation = @vocabulary.constellation
24
+ object_types = constellation.ObjectType.values
25
+ fact_types = constellation.FactType.values
26
+
27
+ # All metamodel object types:
28
+ object_count = 0
29
+ populated_object_type_count = 0
30
+ fact_types_processed = {}
31
+ fact_count = 0
32
+ role_played_count = 0
33
+ constellation.vocabulary.object_type.map do |object_type_name, object_type|
34
+ objects = constellation.send(object_type_name)
35
+ next unless objects.size > 0
36
+ puts "\t#{object_type_name}: #{objects.size} instances (which play #{object_type.all_role.size} roles)"
37
+ populated_object_type_count += 1
38
+ object_count += objects.size
39
+
40
+ #puts "#{object_type_name} has #{object_type.all_role.size} roles"
41
+ object_type.all_role.each do |name, role|
42
+ next unless role.unique
43
+ next if fact_types_processed[role.fact_type]
44
+ next if role.fact_type.is_a?(ActiveFacts::API::TypeInheritanceFactType)
45
+ role_population_count =
46
+ objects.values.inject(0) do |count, object|
47
+ count += 1 if object.send(role.name) != nil
48
+ count
49
+ end
50
+ puts "\t\t#{object_type_name}.#{role.name} has #{role_population_count} instances" if role_population_count > 0
51
+ fact_count += role_population_count
52
+ role_played_count += role_population_count*role.fact_type.all_role.size
53
+
54
+ fact_types_processed[role.fact_type] = true
55
+ end
56
+
57
+ end
58
+ puts "#{@vocabulary.name} has"
59
+ puts "\t#{object_types.size} object types"
60
+ puts "\t#{fact_types.size} fact types"
61
+ puts "\tcompiles to #{object_count} objects in total, of #{populated_object_type_count} metamodel types"
62
+ puts "\tcompiles to #{fact_count} facts in total, of #{fact_types_processed.size} metamodel fact types"
63
+ puts "\tcompiles to #{role_played_count} role instances in total"
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ ActiveFacts::Registry.generator('records', ActiveFacts::Generate::Statistics)
@@ -0,0 +1,265 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Generate metamodel topic hierarchy (topologically sorted) for a compiled vocabulary
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ require 'activefacts/dependency_analyser'
8
+ require 'activefacts/persistence'
9
+
10
+ module ActiveFacts
11
+ module Generate
12
+ # Generate a topic hierarchy of the metamodel constellation created for an ActiveFacts vocabulary.
13
+ # Invoke as
14
+ # afgen --topics <file>.cql
15
+ class Topics
16
+ private
17
+ def initialize(vocabulary)
18
+ @vocabulary = vocabulary
19
+ @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
20
+ end
21
+
22
+ public
23
+ def generate(out = $>)
24
+ @constellation = @vocabulary.constellation
25
+
26
+ @concepts = @constellation.Concept.values.select do |c|
27
+ !c.embodied_as.is_a?(ActiveFacts::Metamodel::Role) &&
28
+ !c.implication_rule
29
+ end
30
+
31
+ @concept_deps = ActiveFacts::DependencyAnalyser.new(@concepts) {|c| c.precursors }
32
+
33
+ # list_concepts(@concept_deps)
34
+ # list_precursors(@concept_deps)
35
+ # list_followers(@concept_deps)
36
+ # list_chasers(@concept_deps)
37
+ # list_ranks(@concept_deps)
38
+
39
+ # @constellation.Topic.values.each do |topic|
40
+ # puts "#{topic.topic_name} depends on #{topic.precursors.map(&:topic_name)*', '}"
41
+ # end
42
+
43
+ # analyse_topic_sequence
44
+
45
+ list_groups_by_rank @concept_deps
46
+
47
+ end
48
+
49
+ def list_concepts concept_deps
50
+ puts "Concepts are"
51
+ concept_deps.each do |concept|
52
+ puts "\t#{concept.describe}"
53
+ end
54
+ puts
55
+ end
56
+
57
+ def list_precursors(concept_deps)
58
+ puts "Precursors are"
59
+ concept_deps.each do |concept|
60
+ next unless (p = concept_deps.precursors(concept)).size > 0
61
+ puts "#{concept.describe}:"
62
+ p.each do |precursor|
63
+ puts "\t#{precursor.describe}"
64
+ end
65
+ end
66
+ end
67
+
68
+ def list_followers(concept_deps)
69
+ puts "Followers are:"
70
+ concept_deps.each do |concept|
71
+ next unless (f = concept_deps.followers(concept)).size > 0
72
+ puts "#{concept.describe}:"
73
+ f.each do |follower|
74
+ puts "\t#{follower.describe}"
75
+ end
76
+ end
77
+ end
78
+
79
+ def list_chasers(concept_deps)
80
+ puts "Chasers are:"
81
+ concept_deps.each do |concept|
82
+ next unless (f = concept_deps.chasers(concept)).size > 0
83
+ puts "#{concept.describe}:"
84
+ f.each do |follower|
85
+ puts "\t#{follower.describe}"
86
+ end
87
+ end
88
+ end
89
+
90
+ def list_ranks(concept_deps)
91
+ puts "Ranks are:"
92
+ page_rank = concept_deps.page_rank
93
+ page_rank.keys.sort_by{|k| -page_rank[k]}.each do |concept|
94
+ puts("%8.4f\t%s" % [page_rank[concept], concept.describe])
95
+ end
96
+ end
97
+
98
+ def analyse_topic_sequence
99
+ if @constellation.Topic.size == 1
100
+ @topics = @constellation.Topic.values.first
101
+ else
102
+ puts "=== Analysing existing topics ==="
103
+ @topic_analyser = ActiveFacts::DependencyAnalyser.new(@constellation.Topic.values) do |topic|
104
+ topic.precursors
105
+ end
106
+
107
+ @topics = []
108
+ failed_topics = @topic_analyser.tsort { |topic|
109
+ @topics << topic
110
+ puts "#{topic.topic_name} including #{topic.all_concept.size} concepts"
111
+ }
112
+ if failed_topics
113
+ puts "Topological sort of topics is impossible"
114
+
115
+ # REVISIT: The strategy here should be to look at the structure of the dependency loop
116
+ # involving the highest-rank topic. Choose the loop member that has fewest dependencies
117
+ # involving individual concepts, and dump that (it will need to flip topic to dump
118
+ # those dependencies). That should break the loop in a minimal way, so we can continue.
119
+
120
+ failed_topics.each do |topic, precursor_topics|
121
+ puts "#{topic.topic_name} depends on:"
122
+ precursor_topics.each do |precursor_topic|
123
+ puts "\t#{precursor_topic.topic_name} because of:"
124
+
125
+ blocking_concept =
126
+ precursor_topic.all_concept.select{|concept|
127
+ concept.precursors.detect{|cp|
128
+ cp.topic == precursor_topic
129
+ }
130
+ }
131
+
132
+ blocking_concept.each do |concept|
133
+ puts "\t\t#{concept.describe} (depends on #{concept.precursors.map(&:topic).uniq.map(&:topic_name).sort.inspect})"
134
+ end
135
+ end
136
+ end
137
+ return
138
+ end
139
+ end
140
+ end
141
+
142
+ def list_groups_by_rank concept_deps
143
+ puts "=== Concepts listed in Ranking Order with chasers ==="
144
+ # List the highest-ranked items that have no un-listed precursors.
145
+ # After each listed item, list the chasers *transitively* before
146
+ # choosing the next highest ranked item.
147
+ @listed = {}
148
+
149
+ start = Time.now
150
+ @page_rank = concept_deps.page_rank # Get the page_rank
151
+ # puts "done in #{Time.now-start}"
152
+
153
+ @ranked = @page_rank.keys.sort_by{|k| -@page_rank[k]} # Sort decreasing
154
+ @topic = nil
155
+ until @listed.size >= @ranked.size
156
+ vt = nil # Capture the first listable ValueType, which we'll choose if we must
157
+ chosen = @ranked.detect do |concept|
158
+ next if @listed[concept]
159
+
160
+ unlisted_precursors =
161
+ concept_deps.precursors_transitive(concept).
162
+ reject do |precursor|
163
+ is_value_type_concept(precursor) or
164
+ precursor == concept or
165
+ @listed[precursor] or
166
+ is_value_type_concept(precursor)
167
+ end
168
+ next if unlisted_precursors.size > 0 # Still has precursors
169
+ vt ||= concept
170
+ next if is_value_type_concept(concept)
171
+ concept
172
+ end
173
+ chosen ||= vt # Choose a value type if there's no other
174
+
175
+ @cluster = []
176
+
177
+ descend concept_deps, chosen
178
+
179
+ cluster_ranks = @cluster.sort_by{|c| -@page_rank[c]}
180
+ head = cluster_ranks.detect{|c| c.object_type} || cluster_ranks.first
181
+ puts "/*\n * #{head.object_type ? head.object_type.name : head.describe}\n */"
182
+ @cluster.each do |concept|
183
+ puts '*'*10+" TOPIC #{@topic = concept.topic.topic_name}" if @topic != concept.topic.topic_name
184
+ puts concept.describe
185
+ end
186
+ puts # Put a line between sections
187
+ end
188
+ end
189
+
190
+ # Descend the concept, also the items that are necessarily dumped with concept, so we don't select them again
191
+ def descend(concept_deps, concept, tab = '')
192
+ @causation ||= []
193
+ @cluster ||= []
194
+ return if @listed[concept] || @causation.include?(concept) # Recursion prevention
195
+ begin
196
+ @causation << concept # Reversed by the "ensure" below.
197
+
198
+ concept_deps.precursors(concept).each do |precursor|
199
+ descend(concept_deps, precursor, tab) unless @listed[precursor] || precursor == concept
200
+ end
201
+
202
+ #puts tab+concept.describe
203
+ @listed[concept] = true
204
+ @cluster << concept
205
+
206
+ body = concept.embodied_as
207
+ case body
208
+ when ActiveFacts::Metamodel::FactType
209
+ body.internal_presence_constraints.each do |pc|
210
+ # puts "Listing #{pc.concept.describe} as internal_presence_constraint of #{concept.describe}"
211
+ descend(concept_deps, pc.concept, tab+"\t")
212
+ end
213
+ if body.entity_type
214
+ # puts "Listing #{body.entity_type.concept.describe} as objectification of #{concept.describe}"
215
+ descend(concept_deps, body.entity_type.concept, tab+"\t")
216
+ end
217
+ body.all_role.each do |role|
218
+ role.all_ring_constraint.each do |rc|
219
+ # puts "Listing #{rc.concept.describe} as ring_constraint of #{concept.describe}"
220
+ descend(concept_deps, rc.concept, tab+"\t")
221
+ end
222
+ end
223
+ when ActiveFacts::Metamodel::EntityType
224
+ body.preferred_identifier.role_sequence.all_role_ref.each do |rr|
225
+ # puts "Listing #{rr.role.fact_type.concept.describe} as existential fact of #{concept.describe}"
226
+ descend(concept_deps, rr.role.fact_type.concept, tab+"\t")
227
+ end
228
+ descend(concept_deps, body.preferred_identifier.concept, tab+"\t")
229
+ when ActiveFacts::Metamodel::ValueType
230
+ if body.value_constraint
231
+ # puts "Listing #{body.value_constraint.concept.describe} as value constraint of #{concept.describe}"
232
+ descend(concept_deps, body.value_constraint.concept, tab+"\t")
233
+ end
234
+ end
235
+
236
+ # Follow up with unlisted chasers (that have no remaining precursors) in order of decreasing importance:
237
+ chasers =
238
+ concept_deps.followers(concept).reject do |follower|
239
+ @listed[follower] or
240
+ is_value_type_concept(follower) or
241
+ concept_deps.precursors(follower).detect{|p| !@listed[p] && !is_value_type_concept(p) } or
242
+ # Exclude subtypes of entity types from the followers:
243
+ concept.object_type && concept.object_type.is_a?(ActiveFacts::Metamodel::EntityType) && concept.object_type.subtypes.include?(follower.object_type)
244
+ end.
245
+ sort_by{|nc| -@page_rank[nc]}
246
+
247
+ chasers.each do |chaser|
248
+ # puts "**** Listing #{chaser.describe} as chaser of #{concept.describe}"
249
+ descend(concept_deps, chaser, tab)
250
+ end
251
+
252
+ ensure
253
+ @causation.pop
254
+ end
255
+ end
256
+
257
+ def is_value_type_concept concept
258
+ o = concept.object_type and o.is_a?(ActiveFacts::Metamodel::ValueType)
259
+ end
260
+
261
+ end
262
+ end
263
+ end
264
+
265
+ ActiveFacts::Registry.generator('topics', ActiveFacts::Generate::Topics)
@@ -0,0 +1,73 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Base class for generators of class libraries in any object-oriented language that supports the ActiveFacts API.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module Generate
9
+ module OOTraits
10
+ module ObjectType
11
+ # Map the ObjectType name to an OO class name
12
+ def oo_type_name
13
+ name.words.capcase
14
+ end
15
+
16
+ # Map the OO class name to a default role name
17
+ def oo_default_role_name
18
+ name.words.snakecase
19
+ end
20
+ end
21
+
22
+ module Role
23
+ def oo_role_definition
24
+ return if fact_type.entity_type
25
+
26
+ if fact_type.all_role.size == 1
27
+ return " maybe :#{preferred_role_name}\n"
28
+ elsif fact_type.all_role.size != 2
29
+ # Shouldn't come here, except perhaps for an invalid model
30
+ return # ternaries and higher are always objectified
31
+ end
32
+
33
+ # REVISIT: TypeInheritance
34
+ if fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
35
+ # trace "Ignoring role #{self} in #{fact_type}, subtype fact type"
36
+ # REVISIT: What about secondary subtypes?
37
+ # REVISIT: What about dumping the relational mapping when using separate tables?
38
+ return
39
+ end
40
+
41
+ return unless is_functional
42
+
43
+ counterpart_role = fact_type.all_role.select{|r| r != self}[0]
44
+ counterpart_type = counterpart_role.object_type
45
+ counterpart_role_name = counterpart_role.preferred_role_name
46
+ counterpart_type_default_role_name = counterpart_type.oo_default_role_name
47
+
48
+ # It's a one_to_one if there's a uniqueness constraint on the other role:
49
+ one_to_one = counterpart_role.is_functional
50
+ return if one_to_one &&
51
+ false # REVISIT: !@object_types_dumped[counterpart_role.object_type]
52
+
53
+ # Find role name:
54
+ role_method = preferred_role_name
55
+ counterpart_role_method = one_to_one ? role_method : "all_"+role_method
56
+ # puts "---"+role.role_name if role.role_name
57
+ if counterpart_role_name != counterpart_type.oo_default_role_name and
58
+ role_method == self.object_type.oo_default_role_name
59
+ # debugger
60
+ counterpart_role_method += "_as_#{counterpart_role_name}"
61
+ end
62
+
63
+ role_name = role_method
64
+ role_name = nil if role_name == object_type.oo_default_role_name
65
+
66
+ as_binary(counterpart_role_name, counterpart_type, is_mandatory, one_to_one, nil, role_name, counterpart_role_method)
67
+ end
68
+ end
69
+
70
+ include ActiveFacts::TraitInjector # Must be last in this module, after all submodules have been defined
71
+ end
72
+ end
73
+ end