activefacts 1.0.2 → 1.1.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.
Files changed (47) hide show
  1. checksums.yaml +5 -13
  2. data/Rakefile +2 -2
  3. data/bin/afgen +1 -1
  4. data/bin/cql +118 -27
  5. data/examples/CQL/Insurance.cql +2 -2
  6. data/examples/CQL/Metamodel.cql +3 -3
  7. data/examples/CQL/SchoolActivities.cql +1 -1
  8. data/examples/CQL/Warehousing.cql +5 -4
  9. data/lib/activefacts/cql.rb +1 -1
  10. data/lib/activefacts/cql/Language/English.treetop +2 -1
  11. data/lib/activefacts/cql/compiler.rb +6 -6
  12. data/lib/activefacts/cql/compiler/clause.rb +69 -46
  13. data/lib/activefacts/cql/compiler/constraint.rb +14 -14
  14. data/lib/activefacts/cql/compiler/entity_type.rb +24 -24
  15. data/lib/activefacts/cql/compiler/fact.rb +40 -27
  16. data/lib/activefacts/cql/compiler/fact_type.rb +16 -16
  17. data/lib/activefacts/cql/compiler/query.rb +12 -12
  18. data/lib/activefacts/cql/compiler/shared.rb +9 -0
  19. data/lib/activefacts/cql/compiler/value_type.rb +4 -4
  20. data/lib/activefacts/cql/parser.rb +9 -9
  21. data/lib/activefacts/generate/cql.rb +41 -20
  22. data/lib/activefacts/generate/helpers/oo.rb +33 -70
  23. data/lib/activefacts/generate/helpers/ordered.rb +61 -87
  24. data/lib/activefacts/generate/ruby.rb +12 -72
  25. data/lib/activefacts/generate/transform/surrogate.rb +13 -13
  26. data/lib/activefacts/input/orm.rb +72 -71
  27. data/lib/activefacts/persistence/columns.rb +66 -31
  28. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  29. data/lib/activefacts/persistence/index.rb +12 -12
  30. data/lib/activefacts/persistence/object_type.rb +15 -12
  31. data/lib/activefacts/persistence/reference.rb +20 -18
  32. data/lib/activefacts/persistence/tables.rb +40 -36
  33. data/lib/activefacts/support.rb +69 -123
  34. data/lib/activefacts/version.rb +2 -2
  35. data/lib/activefacts/vocabulary/extensions.rb +42 -39
  36. data/lib/activefacts/vocabulary/metamodel.rb +11 -1
  37. data/lib/activefacts/vocabulary/verbaliser.rb +28 -28
  38. data/spec/cql/contractions_spec.rb +1 -1
  39. data/spec/cql/entity_type_spec.rb +1 -1
  40. data/spec/cql/fact_type_matching_spec.rb +3 -3
  41. data/spec/cql/role_matching_spec.rb +4 -4
  42. data/spec/cql/samples_spec.rb +2 -2
  43. data/spec/cql_cql_spec.rb +1 -1
  44. data/spec/helpers/array_matcher.rb +1 -1
  45. data/spec/norma_ruby_sql_spec.rb +2 -2
  46. data/spec/norma_tables_spec.rb +3 -2
  47. metadata +47 -68
@@ -29,14 +29,14 @@ module ActiveFacts
29
29
 
30
30
  # Always a table if marked so:
31
31
  if is_independent
32
- debug :absorption, "ValueType #{name} is declared independent"
32
+ trace :absorption, "ValueType #{name} is declared independent"
33
33
  @tentative = false
34
34
  return @is_table = true
35
35
  end
36
36
 
37
37
  # Only a table if it has references (to another ValueType)
38
38
  if !references_from.empty? && !is_auto_assigned
39
- debug :absorption, "#{name} is a table because it has #{references_from.size} references to it"
39
+ trace :absorption, "#{name} is a table because it has #{references_from.size} references to it"
40
40
  @is_table = true
41
41
  else
42
42
  @is_table = false
@@ -57,10 +57,8 @@ module ActiveFacts
57
57
 
58
58
  class EntityType < DomainObjectType
59
59
  # A Reference from an entity type that fully absorbs this one
60
- def absorbed_via; @absorbed_via; end
61
- def absorbed_via=(r) #:nodoc:
62
- @absorbed_via = r
63
- end
60
+ attr_accessor :absorbed_via #:nodoc:
61
+ attr_accessor :absorbed_mirror #:nodoc:
64
62
 
65
63
  def is_auto_assigned #:nodoc:
66
64
  false
@@ -74,14 +72,14 @@ module ActiveFacts
74
72
 
75
73
  # Always a table if marked so
76
74
  if is_independent
77
- debug :absorption, "EntityType #{name} is declared independent"
75
+ trace :absorption, "EntityType #{name} is declared independent"
78
76
  return @is_table = true
79
77
  end
80
78
 
81
79
  # Always a table if nowhere else to go, and has no one-to-ones that might flip:
82
80
  if references_to.empty? and
83
81
  !references_from.detect{|ref| ref.role_type == :one_one }
84
- debug :absorption, "EntityType #{name} is presumed independent as it has nowhere to go"
82
+ trace :absorption, "EntityType #{name} is presumed independent as it has nowhere to go"
85
83
  return @is_table = true
86
84
  end
87
85
 
@@ -92,11 +90,11 @@ module ActiveFacts
92
90
  as_ti = all_supertype_inheritance.detect{|ti| ti.assimilation}
93
91
  @is_table = as_ti != nil
94
92
  if @is_table
95
- debug :absorption, "EntityType #{name} is #{as_ti.assimilation} from supertype #{as_ti.supertype}"
93
+ trace :absorption, "EntityType #{name} is #{as_ti.assimilation} from supertype #{as_ti.supertype}"
96
94
  else
97
95
  identifying_fact_type = preferred_identifier.role_sequence.all_role_ref.to_a[0].role.fact_type
98
96
  if identifying_fact_type.is_a?(TypeInheritance)
99
- debug :absorption, "EntityType #{name} is absorbed into supertype #{supertypes[0].name}"
97
+ trace :absorption, "EntityType #{name} is absorbed into supertype #{supertypes[0].name}"
100
98
  @is_table = false
101
99
  else
102
100
  # Possibly absorbed, we'll have to see how that pans out
@@ -114,7 +112,7 @@ module ActiveFacts
114
112
  next false unless rr.role.object_type.is_a? ValueType
115
113
  rr.role.object_type.is_auto_assigned
116
114
  }
117
- debug :absorption, "#{name} has an auto-assigned counter in its ID, so must be a table"
115
+ trace :absorption, "#{name} has an auto-assigned counter in its ID, so must be a table"
118
116
  @tentative = false
119
117
  return @is_table = true
120
118
  end
@@ -215,7 +213,7 @@ module ActiveFacts
215
213
 
216
214
  populate_all_references
217
215
 
218
- debug :absorption, "Calculating relational composition" do
216
+ trace :absorption, "Calculating relational composition" do
219
217
  # Evaluate the possible independence of each object_type, building an array of object_types of indeterminate status:
220
218
  undecided =
221
219
  all_object_type.select do |object_type|
@@ -223,11 +221,11 @@ module ActiveFacts
223
221
  object_type.tentative # Selection criterion
224
222
  end
225
223
 
226
- if debug :absorption, "Generating tables, #{undecided.size} undecided, already decided ones are"
224
+ if trace :absorption, "Generating tables, #{undecided.size} undecided, already decided ones are"
227
225
  (all_object_type-undecided).each {|object_type|
228
226
  next if ValueType === object_type && !object_type.is_table # Skip unremarkable cases
229
- debug :absorption do
230
- debug :absorption, "#{object_type.name} is #{object_type.is_table ? "" : "not "}a table#{object_type.tentative ? ", tentatively" : ""}"
227
+ trace :absorption do
228
+ trace :absorption, "#{object_type.name} is #{object_type.is_table ? "" : "not "}a table#{object_type.tentative ? ", tentatively" : ""}"
231
229
  end
232
230
  }
233
231
  end
@@ -235,30 +233,34 @@ module ActiveFacts
235
233
  pass = 0
236
234
  begin # Loop while we continue to make progress
237
235
  pass += 1
238
- debug :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
236
+ trace :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
239
237
  possible_flips = {} # A hash by table containing an array of references that can be flipped
240
238
  finalised = # Make an array of things we finalised during this pass
241
239
  undecided.select do |object_type|
242
- debug :absorption, "Considering #{object_type.name}:" do
243
- debug :absorption, "refs to #{object_type.name} are from #{object_type.references_to.map{|ref| ref.from.name}*", "}" if object_type.references_to.size > 0
244
- debug :absorption, "refs from #{object_type.name} are to #{object_type.references_from.map{|ref| ref.to ? ref.to.name : ref.fact_type.default_reading}*", "}" if object_type.references_from.size > 0
240
+ trace :absorption, "Considering #{object_type.name}:" do
241
+ trace :absorption, "refs to #{object_type.name} are from #{object_type.references_to.map{|ref| ref.from.name}*", "}" if object_type.references_to.size > 0
242
+ trace :absorption, "refs from #{object_type.name} are to #{object_type.references_from.map{|ref| ref.to ? ref.to.name : ref.fact_type.default_reading}*", "}" if object_type.references_from.size > 0
245
243
 
246
244
  # Always absorb an objectified unary into its role player:
247
245
  if object_type.fact_type && object_type.fact_type.all_role.size == 1
248
- debug :absorption, "Absorb objectified unary #{object_type.name} into #{object_type.fact_type.entity_type.name}"
246
+ trace :absorption, "Absorb objectified unary #{object_type.name} into #{object_type.fact_type.entity_type.name}"
249
247
  object_type.definitely_not_table
250
248
  next object_type
251
249
  end
252
250
 
253
251
  # If the PI contains one role only, played by an entity type that can absorb us, do that.
254
252
  pi_roles = object_type.preferred_identifier.role_sequence.all_role_ref.map(&:role)
255
- debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.object_type.name}*", "}"
253
+ trace :absorption, "pi_roles are played by #{pi_roles.map{|role| role.object_type.name}*", "}"
256
254
  first_pi_role = pi_roles[0]
257
255
  pi_ref = nil
258
256
  if pi_roles.size == 1 and
259
- object_type.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)}
257
+ object_type.references_to.detect do |ref|
258
+ if ref.from_role == first_pi_role and ref.from.is_a?(EntityType) # and ref.is_mandatory # REVISIT
259
+ pi_ref = ref
260
+ end
261
+ end
260
262
 
261
- debug :absorption, "#{object_type.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
263
+ trace :absorption, "#{object_type.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
262
264
  object_type.definitely_not_table
263
265
  next object_type
264
266
  end
@@ -268,7 +270,7 @@ module ActiveFacts
268
270
  object_type.references_from.reject{|ref|
269
271
  pi_roles.include?(ref.to_role)
270
272
  }
271
- debug :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
273
+ trace :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
272
274
 
273
275
  =begin
274
276
  # This is kinda arbitrary. We need a policy for evaluating optional flips, so we can decide if they "improve" things.
@@ -276,9 +278,9 @@ module ActiveFacts
276
278
 
277
279
  # If all non-identifying functional roles are one-to-ones that can be flipped, do that:
278
280
  if non_identifying_refs_from.all? { |ref| ref.role_type == :one_one && (ref.to.is_table || ref.to.tentative) }
279
- debug :absorption, "Flipping references from #{object_type.name}" do
281
+ trace :absorption, "Flipping references from #{object_type.name}" do
280
282
  non_identifying_refs_from.each do |ref|
281
- debug :absorption, "Flipping #{ref}"
283
+ trace :absorption, "Flipping #{ref}"
282
284
  ref.flip
283
285
  end
284
286
  end
@@ -288,7 +290,7 @@ module ActiveFacts
288
290
 
289
291
  if object_type.references_to.size > 1 and
290
292
  non_identifying_refs_from.size > 0
291
- debug :absorption, "#{object_type.name} has non-identifying functional dependencies so 3NF requires it be a table"
293
+ trace :absorption, "#{object_type.name} has non-identifying functional dependencies so 3NF requires it be a table"
292
294
  object_type.definitely_table
293
295
  next object_type
294
296
  end
@@ -306,15 +308,15 @@ module ActiveFacts
306
308
  to_is_mandatory = !ref.to_role || !!ref.to_role.is_mandatory
307
309
 
308
310
  bad = !(ref.from == object_type ? from_is_mandatory : to_is_mandatory)
309
- debug :absorption, "Not absorbing #{object_type.name} through non-mandatory #{ref}" if bad
311
+ trace :absorption, "Not absorbing #{object_type.name} through non-mandatory #{ref}" if bad
310
312
  bad
311
313
  end
312
314
 
313
315
  # If this object can be fully absorbed, do that (might require flipping some references)
314
316
  if absorption_paths.size > 0
315
- debug :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}"
317
+ trace :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}"
316
318
  absorption_paths.each do |ref|
317
- debug :absorption, "Flipping #{ref} so #{object_type.name} can be absorbed"
319
+ trace :absorption, "Flipping #{ref} so #{object_type.name} can be absorbed"
318
320
  ref.flip if object_type == ref.from
319
321
  end
320
322
  object_type.definitely_not_table
@@ -322,10 +324,12 @@ module ActiveFacts
322
324
  end
323
325
 
324
326
  if non_identifying_refs_from.size == 0
327
+ # REVISIT: This allows absorption along a non-mandatory role of a objectified fact type
328
+ # and object_type.references_to.all?{|ref| ref.is_mandatory }
325
329
  # and (!object_type.is_a?(EntityType) ||
326
330
  # # REVISIT: The roles may be collectively but not individually mandatory.
327
331
  # object_type.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory })
328
- debug :absorption, "#{object_type.name} is fully absorbed in #{object_type.references_to.size} places: #{object_type.references_to.map{|ref| ref.from.name}*", "}"
332
+ trace :absorption, "#{object_type.name} is fully absorbed in #{object_type.references_to.size} places: #{object_type.references_to.map{|ref| ref.from.name}*", "}"
329
333
  object_type.definitely_not_table
330
334
  next object_type
331
335
  end
@@ -335,7 +339,7 @@ module ActiveFacts
335
339
  end
336
340
 
337
341
  undecided -= finalised
338
- debug :absorption, "Finalised #{finalised.size} this pass: #{finalised.map{|f| f.name}*", "}"
342
+ trace :absorption, "Finalised #{finalised.size} this pass: #{finalised.map{|f| f.name}*", "}"
339
343
  end while !finalised.empty?
340
344
 
341
345
  # A ValueType that isn't explicitly a table and isn't needed anywhere doesn't matter,
@@ -343,10 +347,10 @@ module ActiveFacts
343
347
  all_object_type.each do |object_type|
344
348
  if (!object_type.is_table and object_type.references_to.size == 0 and object_type.references_from.size > 0)
345
349
  if !object_type.references_from.detect{|r| !r.is_one_to_one || !r.to.is_table}
346
- debug :absorption, "Flipping references from #{object_type.name}; they're all to tables"
350
+ trace :absorption, "Flipping references from #{object_type.name}; they're all to tables"
347
351
  object_type.references_from.map(&:flip)
348
352
  else
349
- debug :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things"
353
+ trace :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things"
350
354
  object_type.probably_table
351
355
  end
352
356
  end
@@ -354,9 +358,9 @@ module ActiveFacts
354
358
 
355
359
  # Now, evaluate all possibilities of the tentative assignments
356
360
  # Incomplete. Apparently unnecessary as well... so far. We'll see.
357
- if debug :absorption
361
+ if trace :absorption
358
362
  undecided.each do |object_type|
359
- debug :absorption, "Unable to decide independence of #{object_type.name}, going with #{object_type.show_tabular}"
363
+ trace :absorption, "Unable to decide independence of #{object_type.name}, going with #{object_type.show_tabular}"
360
364
  end
361
365
  end
362
366
  end
@@ -1,108 +1,10 @@
1
1
  #
2
2
  # ActiveFacts Support code.
3
- # The debug method supports indented tracing.
4
- # Set the DEBUG environment variable to enable it. Search the code to find the DEBUG keywords, or use "all".
3
+ # The trace method supports indented tracing.
4
+ # Set the TRACE environment variable to enable it. Search the code to find the TRACE keywords, or use "all".
5
5
  #
6
6
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
7
7
  #
8
- #module ActiveFacts
9
- $debug_indent = 0
10
- $debug_nested = false # Set when a block enables all enclosed debugging
11
- $debug_keys = nil
12
- $debug_available = {}
13
-
14
- def debug_initialize
15
- # First time, initialise the tracing environment
16
- $debug_indent = 0
17
- unless $debug_keys
18
- $debug_keys = {}
19
- if (e = ENV["DEBUG"])
20
- e.split(/[^_a-zA-Z0-9]/).each{|k| debug_enable(k) }
21
- if $debug_keys[:help]
22
- at_exit {
23
- $stderr.puts "---\nDebugging keys available: #{$debug_available.keys.map{|s| s.to_s}.sort*", "}"
24
- }
25
- end
26
- end
27
- end
28
- end
29
-
30
- def debug_keys
31
- $debug_available.keys
32
- end
33
-
34
- def debug_enabled key
35
- !key.empty? && $debug_keys[key.to_sym]
36
- end
37
-
38
- def debug_enable key
39
- !key.empty? && $debug_keys[key.to_sym] = true
40
- end
41
-
42
- def debug_disable key
43
- !key.empty? && $debug_keys.delete(key.to_sym)
44
- end
45
-
46
- def debug_toggle key
47
- !key.empty? and debug_enabled(key) ? (debug_disable(key); false) : (debug_enable(key); true)
48
- end
49
-
50
- def debug_selected(args)
51
- # Figure out whether this trace is enabled (itself or by :all), if it nests, and if we should print the key:
52
- key =
53
- if Symbol === args[0]
54
- control = args.shift
55
- if (s = control.to_s) =~ /_\Z/
56
- nested = true
57
- s.sub(/_\Z/, '').to_sym # Avoid creating new strings willy-nilly
58
- else
59
- control
60
- end
61
- else
62
- :all
63
- end
64
-
65
- $debug_available[key] ||= key # Remember that this debug was requested, for help
66
- enabled = $debug_nested || # This debug is enabled because it's in a nested block
67
- $debug_keys[key] || # This debug is enabled in its own right
68
- $debug_keys[:all] # This debug is enabled because all are
69
- $debug_nested = nested
70
- [
71
- (enabled ? 1 : 0),
72
- $debug_keys[:all] ? " %-15s"%control : nil
73
- ]
74
- end
75
-
76
- def debug_show(*args)
77
- unless $debug_keys
78
- debug_initialize
79
- end
80
-
81
- enabled, key_to_show = debug_selected(args)
82
-
83
- # Emit the message if enabled or a parent is:
84
- if args.size > 0 && enabled == 1
85
- puts "\##{key_to_show} " +
86
- ' '*$debug_indent +
87
- args.
88
- # A laudable aim, certainly, but in practise the Procs leak and slow things down:
89
- # map{|a| a.respond_to?(:call) ? a.call : a}.
90
- join(' ')
91
- end
92
- $debug_indent += enabled
93
- enabled
94
- end
95
-
96
- def debug(*args, &block)
97
- begin
98
- old_indent, old_nested, enabled = $debug_indent, $debug_nested, debug_show(*args)
99
- return (block || proc { enabled == 1 }).call
100
- ensure
101
- $debug_indent, $debug_nested = old_indent, old_nested
102
- end
103
- end
104
-
105
- #end
106
8
 
107
9
  # Return all duplicate objects in the array (using hash-equality)
108
10
  class Array
@@ -155,34 +57,78 @@ class Array
155
57
  end
156
58
  end
157
59
 
158
- # Load the ruby debugger before everything else, if requested
159
- if debug :debug or debug :firstaid
160
- begin
161
- require 'ruby-debug'
162
- Debugger.start # (:post_mortem => true) # Some Ruby versions crash on post-mortem debugging
163
- rescue LoadError
164
- # Ok, no debugger, tough luck.
165
- end
60
+ class String
61
+ class Words
62
+ def initialize words
63
+ @words = words
64
+ end
166
65
 
167
- if debug :trap
168
- trap('SIGINT') do
169
- puts "Stopped at:\n\t"+caller*"\n\t"
170
- debugger
171
- true
66
+ def map(&b)
67
+ @words.map(&b)
68
+ end
69
+
70
+ def to_s
71
+ titlecase
72
+ end
73
+
74
+ def titlewords
75
+ @words.map do |word|
76
+ word[0].upcase+word[1..-1].downcase
77
+ end
78
+ end
79
+
80
+ def titlecase
81
+ titlewords.join('')
82
+ end
83
+
84
+ def capwords
85
+ @words.map do |word|
86
+ word[0].upcase+word[1..-1]
87
+ end
88
+ end
89
+
90
+ def capcase
91
+ capwords.join('')
92
+ end
93
+
94
+ def camelwords
95
+ count = 0
96
+ @words.map do |word|
97
+ if (count += 1) == 1
98
+ word
99
+ else
100
+ word[0].upcase+word[1..-1].downcase
101
+ end
102
+ end
172
103
  end
173
- end
174
104
 
175
- if debug :firstaid
176
- puts "Preparing first aid kit"
177
- class ::Exception
178
- alias_method :firstaid_initialize, :initialize
105
+ def camelcase
106
+ camelwords.join('')
107
+ end
179
108
 
180
- def initialize *args, &b
181
- send(:firstaid_initialize, *args, &b)
182
- puts "Stopped due to #{self.class}: #{message} at "+caller*"\n\t"
183
- debugger
184
- true # Exception thrown
109
+ def snakewords
110
+ @words.map do |w|
111
+ w.downcase
185
112
  end
186
113
  end
114
+
115
+ def snakecase
116
+ snakewords.join('_')
117
+ end
118
+
119
+ def to_a
120
+ @words
121
+ end
122
+
123
+ def +(words)
124
+ Words.new(@words + Array(words))
125
+ end
126
+ end
127
+
128
+ def words
129
+ Words.new(
130
+ self.split(/(?:[^[:alnum:]]+|(?<=[[:alnum:]])(?=[[:upper:]][[:lower:]]))/).reject{|w| w == '' }
131
+ )
187
132
  end
188
133
  end
134
+
@@ -7,8 +7,8 @@
7
7
  module ActiveFacts
8
8
  module Version
9
9
  MAJOR = 1
10
- MINOR = 0
11
- PATCH = 2
10
+ MINOR = 1
11
+ PATCH = 0
12
12
 
13
13
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
14
14
  end