activefacts 1.3.0 → 1.5.0

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.
@@ -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