activefacts 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -59,7 +59,7 @@ module ActiveFacts
59
59
  # A Column name is a sequence of names (derived from the to_roles of the References)
60
60
  # joined by a joiner string (pass nil to get the original array of names)
61
61
  def name(joiner = "")
62
- last_name = ""
62
+ last_names = []
63
63
  names = @references.
64
64
  reject do |ref|
65
65
  # Skip any object after the first which is identified by this reference
@@ -67,37 +67,41 @@ module ActiveFacts
67
67
  !ref.fact_type.is_a?(TypeInheritance) and
68
68
  ref.to and
69
69
  ref.to.is_a?(EntityType) and
70
- (role_refs = ref.to.preferred_identifier.role_sequence.all_role_ref).size == 1 and
71
- role_refs.only.role == ref.from_role
70
+ (role_ref = ref.to.preferred_identifier.role_sequence.all_role_ref.single) and
71
+ role_ref.role == ref.from_role
72
72
  end.
73
73
  inject([]) do |a, ref|
74
74
  names = ref.to_names
75
75
 
76
76
  # When traversing type inheritances, keep the subtype name, not the supertype names as well:
77
77
  if a.size > 0 && ref.fact_type.is_a?(TypeInheritance)
78
- a[-1] = names[0] if ref.to == ref.fact_type.subtype # Else we already had the subtype
79
- next a
78
+ next a if ref.to != ref.fact_type.subtype # Did we already have the subtype?
79
+ last_names.size.times { a.pop } # Remove the last names added
80
+ elsif last_names.last && last_names.last == names[0][0...last_names.last.size]
81
+ # When Xyz is followed by XyzID, truncate that to just ID
82
+ names[0] = names[0][last_names.last.size..-1]
83
+ elsif last_names.last == names[0]
84
+ # Same, but where an underscore split up the words
85
+ names.shift
80
86
  end
81
87
 
82
- # When Xyz is followed by XyzID, truncate that to just ID:
83
- names[0] = names[0][last_name.size..-1] if last_name == names[0][0...last_name.size]
84
- last_name = names.last
88
+ # Where the last name is like a reference mode but the preceeding name isn't the identified concept,
89
+ # strip it down (so turn Driver.PartyID into Driver.ID for example):
90
+ if a.size > 0 and
91
+ (et = ref.from).is_a?(EntityType) and
92
+ (role_ref = et.preferred_identifier.role_sequence.all_role_ref.single) and
93
+ role_ref.role == ref.to_role and
94
+ names[0][0...et.name.size].downcase == et.name.downcase
95
+ names[0] = names[0][et.name.size..-1]
96
+ names.shift if names[0] == ""
97
+ end
98
+
99
+ last_names = names
85
100
 
86
101
  a += names
87
102
  a
88
103
  end
89
104
 
90
- # Where the last name is like a reference mode but the preceeding name isn't the identified concept,
91
- # strip it down (so turn Driver.PartyID into Driver.ID for example):
92
- if names.size > 1 and
93
- (et = @references.last.from).is_a?(EntityType) and
94
- (role_refs = et.preferred_identifier.role_sequence.all_role_ref).size == 1 and
95
- role_refs.only.role == @references.last.to_role and
96
- names.last[0...et.name.size].downcase == et.name.downcase
97
- names[-1] = names.last[et.name.size..-1]
98
- names.pop if names.last == ''
99
- end
100
-
101
105
  name_array = names.map{|n| n.sub(/^[a-z]/){|s| s.upcase}}
102
106
  joiner ? name_array * joiner : name_array
103
107
  end
@@ -185,10 +189,13 @@ module ActiveFacts
185
189
  # This section shows the features relevant to relational Persistence.
186
190
  class Concept
187
191
  # The array of columns for this Concept's table
188
- def columns; @columns; end
192
+ def columns
193
+ @columns
194
+ end
189
195
 
190
196
  def populate_columns #:nodoc:
191
- @columns = all_columns({})
197
+ @columns =
198
+ all_columns({})
192
199
  end
193
200
  end
194
201
 
@@ -0,0 +1,73 @@
1
+ module ActiveFacts
2
+ module API
3
+ module Concept
4
+ def table
5
+ @is_table = true
6
+ end
7
+
8
+ def is_table
9
+ @is_table
10
+ end
11
+
12
+ def columns
13
+ #puts "Calculating columns for #{basename}"
14
+ return @columns if @columns
15
+ @columns = (
16
+ roles.
17
+ values.
18
+ select{|role| role.unique}.
19
+ inject([]) do |columns, role|
20
+ rn = role.name.to_s.split(/_/)
21
+ columns += role.counterpart_concept.__absorb(rn, role.counterpart)
22
+ end +
23
+ # REVISIT: Need to use subtypes_transitive here:
24
+ subtypes.
25
+ select{|subtype| !subtype.is_table}. # Don't absorb separate subtypes
26
+ inject([]) { |columns, subtype|
27
+ sn = [subtype.basename]
28
+ columns += subtype.__absorb(sn)
29
+ # puts "subtype #{subtype.name} contributed #{columns.inspect}"
30
+ columns
31
+ }
32
+ ).map{|col_names| col_names.uniq.map{|name| name.sub(/^[a-z]/){|c| c.upcase}}*"."}
33
+ end
34
+
35
+ # Return an array of the absorbed columns, using prefix for name truncation
36
+ def __absorb(prefix, except_role = nil)
37
+ is_entity = respond_to?(:identifying_role_names)
38
+ absorbed_into = nil
39
+ if absorbed_into
40
+ # REVISIT: if this concept is fully absorbed through one of its roles into another table, we absorb that tables identifying_roles
41
+ absorbed_into.__absorb(prefix)
42
+ elsif !@is_table
43
+ # Not a table -> all roles are absorbed
44
+ if is_entity
45
+ roles.
46
+ values.
47
+ select{|role| role.unique && role.counterpart_concept != except_role }.
48
+ inject([]) do |columns, role|
49
+ columns += role.counterpart_concept.__absorb(prefix + role.name.to_s.split(/_/), self)
50
+ end
51
+ else
52
+ [prefix]
53
+ end
54
+ else
55
+ #puts "#{@is_table ? "referencing" : "absorbing"} #{is_entity ? "entity" : "value"} #{basename} using #{prefix.inspect}"
56
+ if is_entity
57
+ identifying_role_names.map{|role_name| prefix+role_name.to_s.split(/_/)}
58
+ else
59
+ # Reference to value type which is a table
60
+ [prefix + ["Value"]]
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ class TrueClass
70
+ def self.__absorb(prefix, except_role = nil)
71
+ [prefix]
72
+ end
73
+ end
@@ -35,7 +35,11 @@ module ActiveFacts
35
35
  # Return an Array of Reference paths for such absorbed FKs
36
36
  def all_absorbed_foreign_key_reference_path
37
37
  references_from.inject([]) do |array, ref|
38
+
38
39
  if ref.is_simple_reference
40
+ # This catches references that would be created to secondary supertypes, when absorption is through primary.
41
+ # There might be other cases where an exclusion like this is needed, but I can't reason it out.
42
+ next array if TypeInheritance === ref.fact_type && absorbed_via && TypeInheritance === absorbed_via.fact_type
39
43
  array << [ref]
40
44
  elsif ref.is_absorbing
41
45
  ref.to.all_absorbed_foreign_key_reference_path.each{|aref|
@@ -102,8 +106,20 @@ module ActiveFacts
102
106
  end
103
107
  debug :fk, "to_columns in #{to.name}: #{to_columns.map { |column| column ? column.name : "OOPS!" }*", "}"
104
108
 
105
- ActiveFacts::Persistence::ForeignKey.new(self, to, fk_ref_path[-1], from_columns, to_columns)
109
+ # Put the column pairs in a defined order, sorting key pairs by to-name:
110
+ froms, tos = from_columns.zip(to_columns).sort_by { |pair|
111
+ pair[1].name(nil)
112
+ }.transpose
113
+
114
+ ActiveFacts::Persistence::ForeignKey.new(self, to, fk_ref_path[-1], froms, tos)
106
115
  end
116
+ end.
117
+ sort_by do |fk|
118
+ # Put the foreign keys in a defined order:
119
+ [ fk.to.name,
120
+ fk.to_columns.map{|col| col.name(nil).sort},
121
+ fk.from_columns.map{|col| col.name(nil).sort}
122
+ ]
107
123
  end
108
124
  end
109
125
  end
@@ -94,10 +94,14 @@ module ActiveFacts
94
94
  # The reference path is the set of absorption references and one past it.
95
95
  # Stopping here means we don't dig into the definitions of FK column counterparts.
96
96
  # Note that many columns of an object may have the same ref_path.
97
+ #
98
+ # REVISIT:
99
+ # Note also that this produces columns ordered for each refpath the same as the
100
+ # order of the columns, not the same as the columns in the PK for which they might be an FK.
97
101
  all_column_by_ref_path =
98
102
  debug :index2, "Indexing columns by ref_path" do
99
- columns.inject({}) do |hash, column|
100
- debug :index2, "References in column #{name}#{column.name}" do
103
+ @columns.inject({}) do |hash, column|
104
+ debug :index2, "References in column #{name}.#{column.name}" do
101
105
  ref_path = column.absorption_references
102
106
  raise "No absorption_references for #{column.name} from #{column.references.map(&:to_s)*" and "}" if !ref_path || ref_path.empty?
103
107
  (hash[ref_path] ||= []) << column
@@ -114,15 +118,17 @@ module ActiveFacts
114
118
  inject({}) do |hash, ref_path|
115
119
  ref_path.each do |ref|
116
120
  next unless ref.to_role
121
+ #debug :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name(".")}*", "}"
122
+ #debugger if name == 'VehicleIncident' && ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
117
123
  ref.to_role.all_role_ref.each do |role_ref|
118
- pcs = role_ref.role_sequence.all_presence_constraint.
124
+ all_pcs = role_ref.role_sequence.all_presence_constraint
125
+ #puts "pcs over #{ref_path.map{|r| r.to_names}.flatten*"."}: #{role_ref.role_sequence.all_presence_constraint.map(&:describe)*"; "}" if all_pcs.size > 0
126
+ pcs = all_pcs.
119
127
  reject do |pc|
120
128
  !pc.max_frequency or # No maximum freq; cannot be a uniqueness constraint
121
129
  pc.max_frequency != 1 or # maximum is not 1
122
- pc.role_sequence.all_role_ref.size == 1 && # UniquenessConstraint is over one role
123
- ((fact_type = pc.role_sequence.all_role_ref.only.role.fact_type).is_a?(TypeInheritance) || # Inheritance
124
- fact_type.all_role.size == 1) # Unary
125
- # The preceeeding two restrictions exclude the internal UCs created within NORMA.
130
+ # Constraint is not over a unary fact type role (NORMA does this)
131
+ pc.role_sequence.all_role_ref.size == 1 && ref_path[-1].to_role.fact_type.all_role.size == 1
126
132
  end
127
133
  next unless pcs.size > 0
128
134
  # The columns for this ref_path support the UCs in "pcs".
@@ -151,8 +157,8 @@ module ActiveFacts
151
157
  # Absorption through a one-to-one forms a UC that we don't need to enforce using an index:
152
158
  next nil if over != self and
153
159
  over.absorbed_via == columns[0].references[absorption_level-1] and
154
- (rrs = uc.role_sequence.all_role_ref).size == 1 and
155
- over.absorbed_via.fact_type.all_role.include?(rrs.only.role)
160
+ (rr = uc.role_sequence.all_role_ref.single) and
161
+ over.absorbed_via.fact_type.all_role.include?(rr.role)
156
162
 
157
163
  index = ActiveFacts::Persistence::Index.new(
158
164
  uc,
@@ -163,7 +169,12 @@ module ActiveFacts
163
169
  )
164
170
  debug :index, index
165
171
  index
166
- end.compact
172
+ end.
173
+ compact.
174
+ sort_by do |index|
175
+ # Put the indices in a defined order:
176
+ index.columns.map(&:name)
177
+ end
167
178
  end
168
179
  end
169
180
 
@@ -114,16 +114,16 @@ module ActiveFacts
114
114
  def to_names
115
115
  case
116
116
  when is_unary
117
- @to_role.fact_type.preferred_reading.reading_text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
117
+ @to_role.fact_type.preferred_reading.reading_text.gsub(/\{[0-9]\}/,'').strip.split(/[_\s]/)
118
118
  when @to && !@to_role # @to is an objectified fact type so @to_role is a phantom
119
- [@to.name]
119
+ @to.name.split(/[_\s]/)
120
120
  when !@to_role # Self-value role of an independent ValueType
121
- ["#{@from.name}Value"]
121
+ ["#{@from.name}", "Value"]
122
122
  when @to_role.role_name # Named role
123
- [@to_role.role_name]
123
+ @to_role.role_name.split(/[_\s]/)
124
124
  else # Use the name from the preferred reading
125
125
  role_ref = @to_role.preferred_reference
126
- [role_ref.leading_adjective, @to_role.concept.name, role_ref.trailing_adjective].compact.map{|w| w.split(/\s/)}.flatten.reject{|s| s == ''}
126
+ [role_ref.leading_adjective, @to_role.concept.name, role_ref.trailing_adjective].compact.map{|w| w.split(/[_\s]/)}.flatten.reject{|s| s == ''}
127
127
  end
128
128
  end
129
129
 
@@ -315,7 +315,7 @@ module ActiveFacts
315
315
  end
316
316
  end
317
317
 
318
- class EntityType
318
+ class EntityType < Concept
319
319
  def populate_references #:nodoc:
320
320
  if fact_type && fact_type.all_role.size > 1
321
321
  # NOT: fact_type.all_role.each do |role| # Place roles in the preferred order instead:
@@ -17,7 +17,7 @@ require 'activefacts/persistence/reference'
17
17
  module ActiveFacts
18
18
  module Metamodel
19
19
 
20
- class ValueType
20
+ class ValueType < Concept
21
21
  def absorbed_via #:nodoc:
22
22
  # ValueTypes aren't absorbed in the way EntityTypes are
23
23
  nil
@@ -55,7 +55,7 @@ module ActiveFacts
55
55
  end
56
56
  end
57
57
 
58
- class EntityType
58
+ class EntityType < Concept
59
59
  # A Reference from an entity type that fully absorbs this one
60
60
  def absorbed_via; @absorbed_via; end
61
61
  def absorbed_via=(r) #:nodoc:
@@ -133,8 +133,8 @@ module ActiveFacts
133
133
  to_1 =
134
134
  all_uniqueness_constraints.
135
135
  detect do |c|
136
- c.role_sequence.all_role_ref.size == 1 and
137
- c.role_sequence.all_role_ref.only.role == self
136
+ (rr = c.role_sequence.all_role_ref.single) and
137
+ rr.role == self
138
138
  end
139
139
 
140
140
  if fact_type.entity_type
@@ -310,7 +310,10 @@ module ActiveFacts
310
310
  populate_all_columns
311
311
  populate_all_indices
312
312
 
313
- @tables = all_feature.select { |f| f.is_table }
313
+ @tables =
314
+ all_feature.
315
+ select { |f| f.is_table }.
316
+ sort_by { |table| table.name }
314
317
  end
315
318
  end
316
319
 
@@ -5,5 +5,5 @@
5
5
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
6
  #
7
7
  module ActiveFacts
8
- VERSION = '0.7.1'
8
+ VERSION = '0.7.2'
9
9
  end
@@ -58,7 +58,7 @@ module ActiveFacts
58
58
  end
59
59
  end
60
60
 
61
- class JoinPath
61
+ class Join
62
62
  def column_name(joiner = '-')
63
63
  concept == input_role.concept ? input_role.preferred_reference.role_name(joiner) : Array(concept.name)
64
64
  end
@@ -72,8 +72,8 @@ module ActiveFacts
72
72
 
73
73
  class RoleRef
74
74
  def describe
75
- # The reference traverses the JoinPaths in sequence to the final role:
76
- all_join_path.sort_by{|jp| jp.join_step}.map{ |jp| jp.describe + "." }*"" + role_name
75
+ # The reference traverses the Joins in sequence to the final role:
76
+ all_join.sort_by{|jp| jp.join_step}.map{ |jp| jp.describe + "." }*"" + role_name
77
77
  end
78
78
 
79
79
  def role_name(joiner = "-")
@@ -86,13 +86,13 @@ module ActiveFacts
86
86
  return joiner ? Array(name_array)*joiner : Array(name_array)
87
87
  end
88
88
 
89
- # Two RoleRefs are equal if they have the same role and JoinPaths with matching roles
89
+ # Two RoleRefs are equal if they have the same role and Joins with matching roles
90
90
  def ==(role_ref)
91
91
  RoleRef === role_ref &&
92
92
  role_ref.role == role &&
93
- all_join_path.size == role_ref.all_join_path.size &&
94
- !all_join_path.sort_by{|j|j.join_step}.
95
- zip(role_ref.all_join_path.sort_by{|j|j.join_step}).
93
+ all_join.size == role_ref.all_join.size &&
94
+ !all_join.sort_by{|j|j.join_step}.
95
+ zip(role_ref.all_join.sort_by{|j|j.join_step}).
96
96
  detect{|j1,j2|
97
97
  j1.input_role != j2.input_role ||
98
98
  j1.output_role != j2.output_role
@@ -376,6 +376,16 @@ module ActiveFacts
376
376
  ((max && min == max) ? "#{max == 1 ? "one" : max.to_s}" : nil)
377
377
  ].compact * " and"
378
378
  end
379
+
380
+ def describe
381
+ role_sequence.describe + " occurs " + frequency + " time"
382
+ end
383
+ end
384
+
385
+ class TypeInheritance
386
+ def describe(role = nil)
387
+ "#{subtype.name} is a kind of #{supertype.name}"
388
+ end
379
389
  end
380
390
 
381
391
  end
@@ -55,6 +55,11 @@ module ActiveFacts
55
55
  value_type :length => 32
56
56
  end
57
57
 
58
+ class Pronoun < String
59
+ value_type
60
+ # REVISIT: Pronoun has restricted values
61
+ end
62
+
58
63
  class ReadingText < String
59
64
  value_type :length => 256
60
65
  end
@@ -90,7 +95,7 @@ module ActiveFacts
90
95
  end
91
96
 
92
97
  class Coefficient
93
- identified_by :numerator, :denominator
98
+ identified_by :numerator, :denominator, :is_precise
94
99
  has_one :denominator # See Denominator.all_coefficient
95
100
  maybe :is_precise
96
101
  has_one :numerator # See Numerator.all_coefficient
@@ -175,11 +180,11 @@ module ActiveFacts
175
180
  one_to_one :unit_id # See UnitId.unit
176
181
  end
177
182
 
178
- class UnitBasis
179
- identified_by :base_unit, :derived_unit
180
- has_one :base_unit, Unit # See Unit.all_unit_basis_as_base_unit
181
- has_one :derived_unit, Unit # See Unit.all_unit_basis_as_derived_unit
182
- has_one :exponent # See Exponent.all_unit_basis
183
+ class Derivation
184
+ identified_by :derived_unit, :base_unit
185
+ has_one :base_unit, Unit # See Unit.all_derivation_as_base_unit
186
+ has_one :derived_unit, Unit # See Unit.all_derivation_as_derived_unit
187
+ has_one :exponent # See Exponent.all_derivation
183
188
  end
184
189
 
185
190
  class ValueRange
@@ -194,7 +199,7 @@ module ActiveFacts
194
199
  end
195
200
 
196
201
  class AllowedRange
197
- identified_by :value_range, :value_restriction
202
+ identified_by :value_restriction, :value_range
198
203
  has_one :value_range # See ValueRange.all_allowed_range
199
204
  has_one :value_restriction # See ValueRestriction.all_allowed_range
200
205
  end
@@ -205,19 +210,19 @@ module ActiveFacts
205
210
  end
206
211
 
207
212
  class Import
208
- identified_by :imported_vocabulary, :vocabulary
213
+ identified_by :vocabulary, :imported_vocabulary
209
214
  has_one :imported_vocabulary, Vocabulary # See Vocabulary.all_import_as_imported_vocabulary
210
215
  has_one :vocabulary # See Vocabulary.all_import
211
216
  end
212
217
 
213
218
  class Feature
214
- identified_by :name, :vocabulary
219
+ identified_by :vocabulary, :name
215
220
  has_one :name # See Name.all_feature
216
221
  has_one :vocabulary # See Vocabulary.all_feature
217
222
  end
218
223
 
219
224
  class Correspondence
220
- identified_by :imported_feature, :import
225
+ identified_by :import, :imported_feature
221
226
  has_one :import # See Import.all_correspondence
222
227
  has_one :imported_feature, Feature # See Feature.all_correspondence_as_imported_feature
223
228
  has_one :local_feature, Feature # See Feature.all_correspondence_as_local_feature
@@ -251,7 +256,7 @@ module ActiveFacts
251
256
 
252
257
  class Concept < Feature
253
258
  maybe :is_independent
254
- maybe :is_personal
259
+ has_one :pronoun # See Pronoun.all_concept
255
260
  end
256
261
 
257
262
  class Role
@@ -272,13 +277,13 @@ module ActiveFacts
272
277
  has_one :trailing_adjective, Adjective # See Adjective.all_role_ref_as_trailing_adjective
273
278
  end
274
279
 
275
- class JoinPath
280
+ class Join
276
281
  identified_by :role_ref, :join_step
277
- has_one :join_step, Ordinal # See Ordinal.all_join_path_as_join_step
278
- has_one :role_ref # See RoleRef.all_join_path
279
- has_one :concept # See Concept.all_join_path
280
- has_one :input_role, Role # See Role.all_join_path_as_input_role
281
- has_one :output_role, Role # See Role.all_join_path_as_output_role
282
+ has_one :join_step, Ordinal # See Ordinal.all_join_as_join_step
283
+ has_one :role_ref # See RoleRef.all_join
284
+ has_one :concept # See Concept.all_join
285
+ has_one :input_role, Role # See Role.all_join_as_input_role
286
+ has_one :output_role, Role # See Role.all_join_as_output_role
282
287
  end
283
288
 
284
289
  class EntityType < Concept