activefacts 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -21,6 +21,7 @@ module ActiveFacts
21
21
  @indices = options.include? "indices"
22
22
  end
23
23
 
24
+ public
24
25
  def generate(out = $>)
25
26
  @vocabulary.tables if @tables || @columns || @indices
26
27
  end
@@ -31,12 +31,13 @@ module ActiveFacts
31
31
  end
32
32
 
33
33
  def vocabulary_start(vocabulary)
34
+ puts "require 'activefacts/api'\n"
34
35
  if @sql
35
36
  require 'activefacts/persistence'
37
+ puts "require 'activefacts/persistence'\n"
36
38
  @tables = vocabulary.tables
37
39
  end
38
- puts "require 'activefacts/api'\n\n"
39
- puts "module #{vocabulary.name}\n\n"
40
+ puts "\nmodule #{vocabulary.name}\n\n"
40
41
  end
41
42
 
42
43
  def vocabulary_end
@@ -63,7 +64,7 @@ module ActiveFacts
63
64
 
64
65
  puts " class #{o.name} < #{ruby_type_name}\n" +
65
66
  " value_type #{params}\n"
66
- puts " table" if @sql and @tables.include? o
67
+ puts " table" if @sql and o.is_table
67
68
  puts " \# REVISIT: #{o.name} has restricted values\n" if o.value_restriction
68
69
  puts " \# REVISIT: #{o.name} is in units of #{o.unit.name}\n" if o.unit
69
70
  roles_dump(o)
@@ -71,9 +72,13 @@ module ActiveFacts
71
72
  end
72
73
 
73
74
  def subtype_dump(o, supertypes, pi = nil)
74
- puts " class #{o.name} < #{ supertypes[0].name }"
75
+ primary_supertype = o && (o.identifying_supertype || o.supertypes[0])
76
+ secondary_supertypes = o.supertypes-[primary_supertype]
77
+
78
+ puts " class #{o.name} < #{ primary_supertype.name }"
75
79
  puts " identified_by #{identified_by(o, pi)}" if pi
76
- puts " table" if @sql and @tables.include? o
80
+ puts " supertypes "+secondary_supertypes.map(&:name)*", " if secondary_supertypes.size > 0
81
+ puts " table" if @sql and o.is_table
77
82
  fact_roles_dump(o.fact_type) if o.fact_type
78
83
  roles_dump(o)
79
84
  puts " end\n\n"
@@ -83,7 +88,7 @@ module ActiveFacts
83
88
  def non_subtype_dump(o, pi)
84
89
  puts " class #{o.name}"
85
90
  puts " identified_by #{identified_by(o, pi)}"
86
- puts " table" if @sql and @tables.include? o
91
+ puts " table" if @sql and o.is_table
87
92
  fact_roles_dump(o.fact_type) if o.fact_type
88
93
  roles_dump(o)
89
94
  puts " end\n\n"
@@ -107,6 +112,7 @@ module ActiveFacts
107
112
  "\n" +
108
113
  secondary_supertypes.map{|sst| " supertype :#{sst.name}"}*"\n" +
109
114
  (pi ? " identified_by #{identified_by(o, pi)}" : "")
115
+ puts " table" if @sql and o.is_table
110
116
  fact_roles_dump(fact_type)
111
117
  roles_dump(o)
112
118
  puts " end\n\n"
@@ -120,32 +126,10 @@ module ActiveFacts
120
126
  }*", "
121
127
  end
122
128
 
123
- def roles_dump(o)
124
- @ar_by_role = nil
125
- if @sql and @tables.include?(o)
126
- ar = o.absorbed_roles
127
- @ar_by_role = ar.all_role_ref.inject({}){|h,rr|
128
- input_role = (j=rr.all_join_path).size > 0 ? j[0].input_role : rr.role
129
- (h[input_role] ||= []) << rr
130
- h
131
- }
132
- #puts ar.all_role_ref.map{|rr| "\t"+rr.describe}*"\n"
133
- end
134
- super
135
- end
136
-
137
129
  def unary_dump(role, role_name)
138
130
  puts " maybe :"+role_name
139
131
  end
140
132
 
141
- def role_dump(role)
142
- other_role = role.fact_type.all_role.select{|r| r != role}[0] || role
143
- if @ar_by_role and @ar_by_role[other_role] and @sql
144
- puts " # role #{role.fact_type.describe(role)}: absorbs in through #{preferred_role_name(other_role)}: "+@ar_by_role[other_role].map(&:column_name)*", "
145
- end
146
- super
147
- end
148
-
149
133
  def binary_dump(role, role_name, role_player, one_to_one = nil, readings = nil, other_role_name = nil, other_method_name = nil)
150
134
  # Find whether we need the name of the other role player, and whether it's defined yet:
151
135
  if role_name.camelcase(true) == role_player.name
@@ -116,7 +116,7 @@ module ActiveFacts
116
116
  tables_emitted = {}
117
117
  delayed_foreign_keys = []
118
118
 
119
- @vocabulary.tables.sort_by{|table| table.name}.each do |table|
119
+ @vocabulary.tables.each do |table|
120
120
  puts "CREATE TABLE #{escape table.name} ("
121
121
 
122
122
  pk = table.identifier_columns
@@ -127,9 +127,9 @@ module ActiveFacts
127
127
  column.references[0].is_simple_reference
128
128
  end
129
129
 
130
- columns = table.columns.sort_by do |column|
131
- column.name(nil)
132
- end.map do |column|
130
+ # We sort the columns here, not in the persistence layer, because it affects
131
+ # the ordering of columns in an index :-(.
132
+ columns = table.columns.sort_by { |column| column.name(nil) }.map do |column|
133
133
  name = escape column.name("")
134
134
  padding = " "*(name.size >= ColumnNameMax ? 1 : ColumnNameMax-name.size)
135
135
  type, params, restrictions = column.type
@@ -158,22 +158,11 @@ module ActiveFacts
158
158
  ")"
159
159
 
160
160
  inline_fks = []
161
- table.foreign_keys.sort_by do |fk|
162
- # Put the Foreign keys in a defined order:
163
- [ fk.to.name,
164
- fk.to_columns.map{|col| col.name(nil).sort},
165
- fk.from_columns.map{|col| col.name(nil).sort}
166
- ]
167
- end.each do |fk|
168
- # Put the column pairs in a defined order, sorting key pairs by to-name:
169
- froms, tos = fk.from_columns.zip(fk.to_columns).sort_by { |pair|
170
- pair[1].name(nil)
171
- }.transpose
172
-
161
+ table.foreign_keys.each do |fk|
173
162
  fk_text = "FOREIGN KEY (" +
174
- froms.map{|column| column.name}*", " +
163
+ fk.from_columns.map{|column| column.name}*", " +
175
164
  ") REFERENCES #{escape fk.to.name} (" +
176
- tos.map{|column| column.name}*", " +
165
+ fk.to_columns.map{|column| column.name}*", " +
177
166
  ")"
178
167
  if !@delay_fks and # We don't want to delay all Fks
179
168
  (tables_emitted[fk.to] or # The target table has been emitted
@@ -187,10 +176,7 @@ module ActiveFacts
187
176
  indices = table.indices
188
177
  inline_indices = []
189
178
  delayed_indices = []
190
- indices.sort_by do |index|
191
- # Put the indices in a defined order:
192
- index.columns.map(&:name)
193
- end.each do |index|
179
+ indices.each do |index|
194
180
  next if index.over == table && index.is_primary # Already did the primary keys
195
181
  abbreviated_column_names = index.abbreviated_column_names*""
196
182
  column_names = index.column_names
@@ -16,6 +16,7 @@ module ActiveFacts
16
16
  @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
17
17
  end
18
18
 
19
+ public
19
20
  def generate(out = $>)
20
21
  out.puts @vocabulary.constellation.verbalise
21
22
  end
@@ -1,5 +1,3 @@
1
- #
2
- # ActiveFacts Vocabulary Input.
3
1
  # Compile a CQL file into an ActiveFacts vocabulary.
4
2
  #
5
3
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
@@ -97,15 +95,15 @@ module ActiveFacts
97
95
  # Create the base type:
98
96
  base_type = nil
99
97
  if (base_type_name != name)
100
- unless base_type = @constellation.ValueType[[@constellation.Name(base_type_name), @vocabulary]]
98
+ unless base_type = @constellation.ValueType[[@vocabulary, @constellation.Name(base_type_name)]]
101
99
  #puts "REVISIT: Creating base ValueType #{base_type_name} in #{@vocabulary.inspect}"
102
- base_type = @constellation.ValueType(base_type_name, @vocabulary)
100
+ base_type = @constellation.ValueType(@vocabulary, base_type_name)
103
101
  return if base_type_name == name
104
102
  end
105
103
  end
106
104
 
107
105
  # Create and initialise the ValueType:
108
- vt = @constellation.ValueType(name, @vocabulary)
106
+ vt = @constellation.ValueType(@vocabulary, name)
109
107
  vt.supertype = base_type if base_type
110
108
  vt.length = length if length
111
109
  vt.scale = scale if scale
@@ -120,7 +118,7 @@ module ActiveFacts
120
118
  min ? [min.to_s, true] : nil,
121
119
  max ? [max.to_s, true] : nil
122
120
  )
123
- ar = @constellation.AllowedRange(v_range, vt.value_restriction)
121
+ ar = @constellation.AllowedRange(vt.value_restriction, v_range)
124
122
  end
125
123
  end
126
124
  end
@@ -130,7 +128,7 @@ module ActiveFacts
130
128
  debug :entity, "Defining Entity Type #{name}" do
131
129
  # Assert the entity:
132
130
  # If this entity was forward referenced, this won't be a new object, and will subsume its roles
133
- entity_type = @constellation.EntityType(name, @vocabulary)
131
+ entity_type = @constellation.EntityType(@vocabulary, name)
134
132
 
135
133
  # Set up its supertypes:
136
134
  supertypes.each do |supertype_name|
@@ -162,9 +160,9 @@ module ActiveFacts
162
160
 
163
161
  # Find or Create an appropriate ValueType called "#{name}#{mode}", of the supertype "#{mode}"
164
162
  vt_name = "#{name}#{mode}"
165
- unless vt = @constellation.ValueType[[vt_name, @vocabulary]]
166
- base_vt = @constellation.ValueType(mode, @vocabulary)
167
- vt = @constellation.ValueType(vt_name, @vocabulary, :supertype => base_vt)
163
+ unless vt = @constellation.ValueType[[@vocabulary, vt_name]]
164
+ base_vt = @constellation.ValueType(@vocabulary, mode)
165
+ vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt)
168
166
  end
169
167
  end
170
168
 
@@ -373,7 +371,7 @@ module ActiveFacts
373
371
 
374
372
  def add_supertype(entity_type, supertype_name, identifying_supertype)
375
373
  debug :supertype, "Supertype #{supertype_name}"
376
- supertype = @constellation.EntityType(supertype_name, @vocabulary)
374
+ supertype = @constellation.EntityType(@vocabulary, supertype_name)
377
375
  inheritance_fact = @constellation.TypeInheritance(entity_type, supertype, :fact_type_id => :new)
378
376
 
379
377
  # Create a reading:
@@ -459,7 +457,7 @@ module ActiveFacts
459
457
 
460
458
  # The fact type has a name iff it's objectified as an entity type
461
459
  #puts "============= Creating entity #{name} to nominalize fact type #{fact_type.default_reading} ======================" if name
462
- fact_type.entity_type = @constellation.EntityType(name, @vocabulary) if name
460
+ fact_type.entity_type = @constellation.EntityType(@vocabulary, name) if name
463
461
 
464
462
  # Add the identifying PresenceConstraint for this fact type:
465
463
  if fact_type.all_role.size == 1 && !fact_type.entity_type
@@ -496,7 +494,7 @@ module ActiveFacts
496
494
  # sequences. Each binding that isn't common at this top level
497
495
  # must occur more than once in each group of fact types where
498
496
  # it appears, and it forms a join between those fact types.
499
- def bind_join_paths_as_role_sequences(readings_list)
497
+ def bind_joins_as_role_sequences(readings_list)
500
498
  @symbols = SymbolTable.new(@constellation, @vocabulary)
501
499
  fact_roles_list = []
502
500
  bindings_list = []
@@ -520,8 +518,8 @@ module ActiveFacts
520
518
  end
521
519
 
522
520
  # Each set of binding arrays in the list must share at least one common binding
523
- bindings_by_join_path = bindings_list.map{|join_path| join_path.flatten}
524
- common_bindings = bindings_by_join_path[1..-1].inject(bindings_by_join_path[0]) { |c, b| c & b }
521
+ bindings_by_join = bindings_list.map{|join| join.flatten}
522
+ common_bindings = bindings_by_join[1..-1].inject(bindings_by_join[0]) { |c, b| c & b }
525
523
  # Was:
526
524
  # common_bindings = bindings_list.inject(bindings_list[0]) { |common, bindings| common & bindings }
527
525
  raise "Set constraints must have at least one common role between the sets" unless common_bindings.size > 0
@@ -533,9 +531,9 @@ module ActiveFacts
533
531
  # Each element of a join path is the array of bindings for a fact type invocation.
534
532
  # Each invocation must share a binding (not one of the globally common ones) with
535
533
  # another invocation in that join path.
536
- bindings_list.each_with_index do |join_path, jpnum|
534
+ bindings_list.each_with_index do |join, jpnum|
537
535
  # Check that this bindings array creates a complete join path:
538
- join_path.each_with_index do |bindings, i|
536
+ join.each_with_index do |bindings, i|
539
537
  fact_type_roles = fact_roles_list[jpnum][i]
540
538
  fact_type = fact_type_roles[0].fact_type
541
539
 
@@ -543,10 +541,10 @@ module ActiveFacts
543
541
  # These bindings must be joined to some later fact type by a common binding that isn't a globally-common one:
544
542
  local_bindings = bindings-common_bindings
545
543
  next if local_bindings.size == 0 # No join path is required, as only one fact type is invoked.
546
- next if i == join_path.size-1 # We already checked that the last fact type invocation is joined
544
+ next if i == join.size-1 # We already checked that the last fact type invocation is joined
547
545
  ok = local_bindings.detect do |local_binding|
548
546
  j = i+1
549
- join_path[j..-1].detect do |other_bindings|
547
+ join[j..-1].detect do |other_bindings|
550
548
  other_fact_type_roles = fact_roles_list[jpnum][j]
551
549
  other_fact_type = other_fact_type_roles[0].fact_type
552
550
  j += 1
@@ -568,10 +566,10 @@ module ActiveFacts
568
566
  role_sequences = readings_list.map{|r| @constellation.RoleSequence(:new) }
569
567
  common_bindings.each_with_index do |binding, index|
570
568
  role_sequences.each_with_index do |rs, rsi|
571
- join_path = bindings_list[rsi]
569
+ join = bindings_list[rsi]
572
570
  fact_pos = nil
573
- join_pos = (0...join_path.size).detect do |i|
574
- fact_pos = join_path[i].index(binding)
571
+ join_pos = (0...join.size).detect do |i|
572
+ fact_pos = join[i].index(binding)
575
573
  end
576
574
  @constellation.RoleRef(rs, index).role = fact_roles_list[rsi][join_pos][fact_pos]
577
575
  end
@@ -581,7 +579,7 @@ module ActiveFacts
581
579
  end
582
580
 
583
581
  def subset_constraint(subset_readings, superset_readings)
584
- role_sequences = bind_join_paths_as_role_sequences([subset_readings, superset_readings])
582
+ role_sequences = bind_joins_as_role_sequences([subset_readings, superset_readings])
585
583
 
586
584
  #puts "subset_constraint:\n\t#{subset_readings.inspect}\n\t#{superset_readings.inspect}"
587
585
  #puts "\t#{role_sequences.map{|rs| rs.describe}.inspect}"
@@ -601,7 +599,7 @@ module ActiveFacts
601
599
  # Exactly one or at most one, nothing else will do
602
600
  raise "Set comparison constraint must use 'at most' or 'exactly' one" if quantifier[1] != 1
603
601
 
604
- role_sequences = bind_join_paths_as_role_sequences(readings_list)
602
+ role_sequences = bind_joins_as_role_sequences(readings_list)
605
603
 
606
604
  # Create the constraint:
607
605
  constraint = @constellation.SetExclusionConstraint(:new)
@@ -615,7 +613,7 @@ module ActiveFacts
615
613
  def equality_constraint(*readings_list)
616
614
  #puts "REVISIT: equality\n\t#{readings_list.map{|rl| rl.inspect}*"\n\tif and only if\n\t"}"
617
615
 
618
- role_sequences = bind_join_paths_as_role_sequences(readings_list)
616
+ role_sequences = bind_joins_as_role_sequences(readings_list)
619
617
 
620
618
  # Create the constraint:
621
619
  constraint = @constellation.SetEqualityConstraint(:new)
@@ -994,7 +992,7 @@ module ActiveFacts
994
992
  return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
995
993
  roles[0].all_role_ref.each do |role_ref|
996
994
  next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
997
- pc = role_ref.role_sequence.all_presence_constraint.only # Will throw an exception if there's more than one.
995
+ pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
998
996
  #puts "Existing PresenceConstraint matches those roles!" if pc
999
997
  return pc if pc
1000
998
  end
@@ -1018,15 +1016,15 @@ module ActiveFacts
1018
1016
  end
1019
1017
 
1020
1018
  def concept_by_name(name)
1021
- player = @constellation.Concept[[name, @vocabulary.identifying_role_values]]
1019
+ player = @constellation.Concept[[@vocabulary.identifying_role_values, name]]
1022
1020
 
1023
1021
  # REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
1024
1022
  if !player && %w{Date DateAndTime Time}.include?(name)
1025
- player = @constellation.ValueType(name, @vocabulary.identifying_role_values)
1023
+ player = @constellation.ValueType(@vocabulary.identifying_role_values, name)
1026
1024
  end
1027
1025
 
1028
1026
  if (!player && @symbols.allowed_forward[name])
1029
- player = @constellation.EntityType(name, @vocabulary)
1027
+ player = @constellation.EntityType(@vocabulary, name)
1030
1028
  end
1031
1029
  player
1032
1030
  end
@@ -1173,15 +1171,15 @@ module ActiveFacts
1173
1171
  # return the EntityType or ValueType this name refers to:
1174
1172
  def concept(name, allowed_forward = false)
1175
1173
  # See if the name is a defined concept in this vocabulary:
1176
- player = @constellation.Concept[[name, virv = @vocabulary.identifying_role_values]]
1174
+ player = @constellation.Concept[[virv = @vocabulary.identifying_role_values, name]]
1177
1175
 
1178
1176
  # REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
1179
1177
  if !player && %w{Date DateAndTime Time}.include?(name)
1180
- player = @constellation.ValueType(name, virv)
1178
+ player = @constellation.ValueType(virv, name)
1181
1179
  end
1182
1180
 
1183
1181
  if !player && allowed_forward
1184
- player = @constellation.EntityType(name, @vocabulary)
1182
+ player = @constellation.EntityType(@vocabulary, name)
1185
1183
  end
1186
1184
 
1187
1185
  player
@@ -101,11 +101,11 @@ module ActiveFacts
101
101
  entity_types <<
102
102
  @by_id[id] =
103
103
  entity_type =
104
- @constellation.EntityType(name, @vocabulary)
104
+ @constellation.EntityType(@vocabulary, name)
105
105
  independent = x.attributes['IsIndependent']
106
106
  entity_type.is_independent = true if independent && independent == 'true'
107
107
  personal = x.attributes['IsPersonal']
108
- entity_type.is_personal = true if personal && personal == 'true'
108
+ entity_type.pronoun = 'personal' if personal && personal == 'true'
109
109
  # x_pref = x.elements.to_a("orm:PreferredIdentifier")[0]
110
110
  # if x_pref
111
111
  # pi_id = x_pref.attributes['ref']
@@ -139,26 +139,26 @@ module ActiveFacts
139
139
  length = 32 if type_name =~ /Integer\Z/ && length.to_i == 0 # Set default integer length
140
140
 
141
141
  # REVISIT: Need to handle standard types better here:
142
- data_type = type_name != name ? @constellation.ValueType(type_name, @vocabulary) : nil
142
+ data_type = type_name != name ? @constellation.ValueType(@vocabulary, type_name) : nil
143
143
 
144
144
  # puts "ValueType #{name} is #{id}"
145
145
  value_types <<
146
146
  @by_id[id] =
147
- vt = @constellation.ValueType(name, @vocabulary)
147
+ vt = @constellation.ValueType(@vocabulary, name)
148
148
  vt.supertype = data_type
149
149
  vt.length = length if length
150
150
  vt.scale = scale if scale
151
151
  independent = x.attributes['IsIndependent']
152
152
  vt.is_independent = true if independent && independent == 'true'
153
153
  personal = x.attributes['IsPersonal']
154
- vt.is_personal = true if personal && personal == 'true'
154
+ vt.pronoun = 'personal' if personal && personal == 'true'
155
155
 
156
156
  x_ranges = x.elements.to_a("orm:ValueRestriction/orm:ValueConstraint/orm:ValueRanges/orm:ValueRange")
157
157
  next if x_ranges.size == 0
158
158
  vt.value_restriction = @constellation.ValueRestriction(:new)
159
159
  x_ranges.each{|x_range|
160
160
  v_range = value_range(x_range)
161
- ar = @constellation.AllowedRange(v_range, vt.value_restriction)
161
+ ar = @constellation.AllowedRange(vt.value_restriction, v_range)
162
162
  }
163
163
  }
164
164
  end
@@ -317,7 +317,7 @@ module ActiveFacts
317
317
  #puts "NestedType #{name} is #{id}, nests #{fact_type.fact_type_id}"
318
318
  nested_types <<
319
319
  @by_id[id] =
320
- nested_type = @constellation.EntityType(name, @vocabulary)
320
+ nested_type = @constellation.EntityType(@vocabulary, name)
321
321
  nested_type.fact_type = fact_type
322
322
  end
323
323
  }
@@ -390,7 +390,7 @@ module ActiveFacts
390
390
  role.role_value_restriction = @constellation.ValueRestriction(:new)
391
391
  x_ranges.each{|x_range|
392
392
  v_range = value_range(x_range)
393
- ar = @constellation.AllowedRange(v_range, role.role_value_restriction)
393
+ ar = @constellation.AllowedRange(role.role_value_restriction, v_range)
394
394
  }
395
395
  }
396
396
 
@@ -632,8 +632,8 @@ module ActiveFacts
632
632
  # A TypeInheritance fact type has a uniqueness constraint on each role.
633
633
  # If this UC is on the supertype and identifies the subtype, it's preferred:
634
634
  is_supertype_constraint =
635
- roles.all_role_ref.size == 1 &&
636
- (role = roles.all_role_ref.only.role) &&
635
+ (rr = roles.all_role_ref.single) &&
636
+ (role = rr.role) &&
637
637
  (fact_type = role.fact_type) &&
638
638
  fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) &&
639
639
  role.concept == fact_type.supertype &&
@@ -649,8 +649,7 @@ module ActiveFacts
649
649
  pc.is_preferred_identifier = true if pi || unary_identifier || is_supertype_constraint
650
650
  #puts "#{name} covers #{roles.describe} has min=#{pc.min_frequency}, max=1, preferred=#{pc.is_preferred_identifier.inspect}" if emit_special_debug
651
651
 
652
- #puts roles.verbalise
653
- #puts pc.verbalise
652
+ #puts roles.all_role_ref.to_a[0].role.fact_type.describe + " is subject to " + pc.describe if roles.all_role_ref.all?{|r| r.role.fact_type.is_a? ActiveFacts::Metamodel::TypeInheritance }
654
653
 
655
654
  (@constraints_by_rs[roles] ||= []) << pc
656
655
  }
@@ -3,8 +3,13 @@
3
3
  #
4
4
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
5
5
  #
6
+
7
+ # These files are concerned with calculating a relational schema for a vocabulary:
6
8
  require 'activefacts/persistence/reference'
7
9
  require 'activefacts/persistence/tables'
8
10
  require 'activefacts/persistence/columns'
9
11
  require 'activefacts/persistence/foreignkey'
10
12
  require 'activefacts/persistence/index'
13
+
14
+ # These extend the API classes with relational awareness:
15
+ require 'activefacts/persistence/concept'