activefacts 0.8.16 → 0.8.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +15 -0
  2. data/Manifest.txt +10 -4
  3. data/bin/afgen +26 -20
  4. data/bin/cql +1 -1
  5. data/css/orm2.css +89 -9
  6. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  7. data/examples/CQL/Genealogy.cql +5 -5
  8. data/examples/CQL/Metamodel.cql +121 -91
  9. data/examples/CQL/MonthInSeason.cql +2 -6
  10. data/examples/CQL/SeparateSubtype.cql +11 -9
  11. data/examples/CQL/ServiceDirector.cql +21 -33
  12. data/examples/CQL/Supervision.cql +0 -3
  13. data/examples/CQL/WindowInRoomInBldg.cql +10 -4
  14. data/examples/CQL/unit.cql +1 -1
  15. data/lib/activefacts.rb +1 -0
  16. data/lib/activefacts/cql/CQLParser.treetop +5 -1
  17. data/lib/activefacts/cql/Context.treetop +2 -7
  18. data/lib/activefacts/cql/Expressions.treetop +2 -2
  19. data/lib/activefacts/cql/FactTypes.treetop +37 -31
  20. data/lib/activefacts/cql/Language/English.treetop +21 -4
  21. data/lib/activefacts/cql/LexicalRules.treetop +59 -1
  22. data/lib/activefacts/cql/ObjectTypes.treetop +22 -12
  23. data/lib/activefacts/cql/Terms.treetop +13 -9
  24. data/lib/activefacts/cql/ValueTypes.treetop +30 -11
  25. data/lib/activefacts/cql/compiler.rb +34 -5
  26. data/lib/activefacts/cql/compiler/clause.rb +207 -116
  27. data/lib/activefacts/cql/compiler/constraint.rb +129 -105
  28. data/lib/activefacts/cql/compiler/entity_type.rb +49 -27
  29. data/lib/activefacts/cql/compiler/expression.rb +71 -42
  30. data/lib/activefacts/cql/compiler/fact.rb +70 -64
  31. data/lib/activefacts/cql/compiler/fact_type.rb +108 -57
  32. data/lib/activefacts/cql/compiler/query.rb +178 -0
  33. data/lib/activefacts/cql/compiler/shared.rb +13 -12
  34. data/lib/activefacts/cql/compiler/value_type.rb +10 -4
  35. data/lib/activefacts/cql/nodes.rb +1 -1
  36. data/lib/activefacts/cql/parser.rb +6 -2
  37. data/lib/activefacts/generate/absorption.rb +6 -3
  38. data/lib/activefacts/generate/cql.rb +140 -84
  39. data/lib/activefacts/generate/dm.rb +12 -6
  40. data/lib/activefacts/generate/help.rb +25 -6
  41. data/lib/activefacts/generate/helpers/oo.rb +195 -0
  42. data/lib/activefacts/generate/helpers/ordered.rb +589 -0
  43. data/lib/activefacts/generate/helpers/rails.rb +57 -0
  44. data/lib/activefacts/generate/html/glossary.rb +274 -54
  45. data/lib/activefacts/generate/json.rb +25 -22
  46. data/lib/activefacts/generate/null.rb +1 -0
  47. data/lib/activefacts/generate/rails/models.rb +244 -0
  48. data/lib/activefacts/generate/rails/schema.rb +185 -0
  49. data/lib/activefacts/generate/records.rb +1 -0
  50. data/lib/activefacts/generate/ruby.rb +51 -30
  51. data/lib/activefacts/generate/sql/mysql.rb +5 -3
  52. data/lib/activefacts/generate/sql/server.rb +8 -4
  53. data/lib/activefacts/generate/text.rb +1 -0
  54. data/lib/activefacts/generate/transform/surrogate.rb +209 -0
  55. data/lib/activefacts/generate/version.rb +1 -0
  56. data/lib/activefacts/input/orm.rb +234 -181
  57. data/lib/activefacts/mapping/rails.rb +122 -0
  58. data/lib/activefacts/persistence/columns.rb +34 -18
  59. data/lib/activefacts/persistence/foreignkey.rb +129 -71
  60. data/lib/activefacts/persistence/index.rb +42 -12
  61. data/lib/activefacts/persistence/reference.rb +37 -23
  62. data/lib/activefacts/persistence/tables.rb +53 -19
  63. data/lib/activefacts/registry.rb +11 -0
  64. data/lib/activefacts/support.rb +28 -10
  65. data/lib/activefacts/version.rb +1 -1
  66. data/lib/activefacts/vocabulary/extensions.rb +246 -117
  67. data/lib/activefacts/vocabulary/metamodel.rb +105 -65
  68. data/lib/activefacts/vocabulary/verbaliser.rb +226 -194
  69. data/spec/absorption_spec.rb +1 -0
  70. data/spec/cql/comparison_spec.rb +8 -8
  71. data/spec/cql/contractions_spec.rb +16 -43
  72. data/spec/cql/entity_type_spec.rb +2 -1
  73. data/spec/cql/expressions_spec.rb +2 -2
  74. data/spec/cql/fact_type_matching_spec.rb +4 -1
  75. data/spec/cql/parser/bad_literals_spec.rb +30 -30
  76. data/spec/cql/parser/entity_types_spec.rb +6 -6
  77. data/spec/cql/parser/expressions_spec.rb +25 -19
  78. data/spec/cql/samples_spec.rb +5 -4
  79. data/spec/cql_cql_spec.rb +2 -1
  80. data/spec/cql_dm_spec.rb +4 -0
  81. data/spec/cql_mysql_spec.rb +4 -0
  82. data/spec/cql_parse_spec.rb +2 -0
  83. data/spec/cql_ruby_spec.rb +4 -0
  84. data/spec/cql_sql_spec.rb +4 -0
  85. data/spec/cqldump_spec.rb +7 -4
  86. data/spec/helpers/parse_to_ast_matcher.rb +7 -3
  87. data/spec/helpers/test_parser.rb +2 -0
  88. data/spec/norma_cql_spec.rb +5 -2
  89. data/spec/norma_ruby_spec.rb +4 -1
  90. data/spec/norma_ruby_sql_spec.rb +4 -1
  91. data/spec/norma_sql_spec.rb +4 -1
  92. data/spec/norma_tables_spec.rb +2 -2
  93. data/spec/ruby_api_spec.rb +1 -1
  94. data/spec/spec_helper.rb +2 -0
  95. data/spec/transform_surrogate_spec.rb +59 -0
  96. metadata +70 -60
  97. data/TODO +0 -308
  98. data/lib/activefacts/cql/compiler/join.rb +0 -162
  99. data/lib/activefacts/generate/oo.rb +0 -176
  100. data/lib/activefacts/generate/ordered.rb +0 -602
@@ -43,3 +43,4 @@ module ActiveFacts
43
43
  end
44
44
  end
45
45
 
46
+ ActiveFacts::Registry.generator('records', ActiveFacts::Generate::RECORDS)
@@ -4,8 +4,10 @@
4
4
  #
5
5
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
6
  #
7
+ require 'activefacts'
7
8
  require 'activefacts/vocabulary'
8
- require 'activefacts/generate/oo'
9
+ require 'activefacts/generate/helpers/oo'
10
+ require 'activefacts/mapping/rails'
9
11
 
10
12
  module ActiveFacts
11
13
  module Generate
@@ -15,25 +17,29 @@ module ActiveFacts
15
17
  # Options are comma or space separated:
16
18
  # * help list available options
17
19
  # * sql Emit the sql mapping for tables/columns (REVISIT: not functional at present)
18
- class RUBY < OO
20
+ class RUBY < Helpers::OO
19
21
  private
20
22
 
21
23
  def set_option(option)
22
- @sql ||= false
24
+ @mapping = false
23
25
  case option
24
26
  when 'help', '?'
25
27
  $stderr.puts "Usage:\t\tafgen --ruby[=option,option] input_file.cql\n"+
26
- "options:\tsql\tEmit data to enable mappings to SQL"
28
+ "\t\tmapping={sql|rails}\tEmit data to enable mappings to SQL or to Rails"
27
29
  exit 0
28
- when 'sql'; @sql = true
30
+ when /mapping=(.*)/
31
+ @mapping = $1
32
+ @vocabulary.tables
29
33
  else super
30
34
  end
31
35
  end
32
36
 
33
37
  def vocabulary_start(vocabulary)
34
38
  puts "require 'activefacts/api'\n"
35
- if @sql
39
+ if @mapping
36
40
  require 'activefacts/persistence'
41
+ end
42
+ if @mapping == 'sql'
37
43
  puts "require 'activefacts/persistence'\n"
38
44
  @tables = vocabulary.tables
39
45
  end
@@ -44,6 +50,15 @@ module ActiveFacts
44
50
  puts "end"
45
51
  end
46
52
 
53
+ def emit_mapping o
54
+ case @mapping
55
+ when 'sql'
56
+ puts " table"
57
+ when 'rails'
58
+ puts " table :#{o.rails_name}"
59
+ end
60
+ end
61
+
47
62
  def value_type_dump(o)
48
63
  length = (l = o.length) && l > 0 ? ":length => #{l}" : nil
49
64
  scale = (s = o.scale) && s > 0 ? ":scale => #{s}" : nil
@@ -69,9 +84,7 @@ module ActiveFacts
69
84
 
70
85
  puts " class #{name} < #{ruby_type_name}\n" +
71
86
  " value_type #{params}\n"
72
- if @sql and o.is_table
73
- puts " table"
74
- end
87
+ emit_mapping o if o.is_table
75
88
  puts " restrict #{o.value_constraint.all_allowed_range_sorted.map{|ar| ar.to_s}*", "}\n" if o.value_constraint
76
89
  puts " \# REVISIT: #{o.name} is in units of #{o.unit.name}\n" if o.unit
77
90
  roles_dump(o)
@@ -85,9 +98,7 @@ module ActiveFacts
85
98
  puts " class #{o.name.gsub(/ /,'')} < #{ primary_supertype.name.gsub(/ /,'') }"
86
99
  puts " identified_by #{identified_by(o, pi)}" if pi
87
100
  puts " supertypes "+secondary_supertypes.map{|st| st.name.gsub(/ /,'')}*", " if secondary_supertypes.size > 0
88
- if @sql and o.is_table
89
- puts " table"
90
- end
101
+ emit_mapping(o) if o.is_table
91
102
  fact_roles_dump(o.fact_type) if o.fact_type
92
103
  roles_dump(o)
93
104
  puts " end\n\n"
@@ -99,9 +110,7 @@ module ActiveFacts
99
110
 
100
111
  # We want to name the absorption role only when it's absorbed along its single identifying role.
101
112
  puts " identified_by #{identified_by(o, pi)}"
102
- if @sql and o.is_table
103
- puts " table"
104
- end
113
+ emit_mapping o if o.is_table
105
114
  fact_roles_dump(o.fact_type) if o.fact_type
106
115
  roles_dump(o)
107
116
  puts " end\n\n"
@@ -125,7 +134,7 @@ module ActiveFacts
125
134
  "\n" +
126
135
  secondary_supertypes.map{|sst| " supertype :#{sst.name.gsub(/ /,'_')}"}*"\n" +
127
136
  (pi ? " identified_by #{identified_by(o, pi)}" : "")
128
- puts " table" if @sql and o.is_table
137
+ emit_mapping o if o.is_table
129
138
  fact_roles_dump(fact_type)
130
139
  roles_dump(o)
131
140
  puts " end\n\n"
@@ -144,25 +153,35 @@ module ActiveFacts
144
153
  end
145
154
 
146
155
  def binary_dump(role, role_name, role_player, mandatory = nil, one_to_one = nil, readings = nil, other_role_name = nil, other_method_name = nil)
156
+ ruby_role_name = ":"+role_name.gsub(/ /,'_')
157
+
147
158
  # Find whether we need the name of the other role player, and whether it's defined yet:
148
- if role_name.camelcase == role_player.name.gsub(/ /,'').sub(/^[a-z]/) {|i| i.upcase}
149
- # Don't use Class name if implied by rolename
150
- role_reference = nil
151
- else
159
+ implied_role_name = role_player.name.gsub(/ /,'').sub(/^[a-z]/) {|i| i.upcase}
160
+ if role_name.camelcase != implied_role_name
161
+ # Only use Class name if it's not implied by the rolename
152
162
  role_reference = ":class => "+object_type_reference(role_player)
153
163
  end
164
+
154
165
  other_role_name = ":counterpart => :"+other_role_name.gsub(/ /,'_') if other_role_name
155
166
 
156
- line = " #{one_to_one ? "one_to_one" : "has_one" } " +
157
- [ ":"+role_name.gsub(/ /,'_'),
158
- role_reference,
159
- mandatory ? ":mandatory => true" : nil,
160
- readings,
161
- other_role_name,
162
- (vr = role.role_value_constraint) ? ":restrict => #{vr}" : nil
163
- ].compact*", "+" "
164
- line += " "*(48-line.length) if line.length < 48
165
- line += "\# See #{role_player.name.gsub(/ /,'')}.#{other_method_name}" if other_method_name
167
+ if vr = role.role_value_constraint
168
+ value_restriction = ":restrict => #{vr}"
169
+ end
170
+
171
+ options = [
172
+ ruby_role_name,
173
+ role_reference,
174
+ mandatory ? ":mandatory => true" : nil,
175
+ readings,
176
+ other_role_name,
177
+ value_restriction
178
+ ].compact
179
+
180
+ line = " #{one_to_one ? "one_to_one" : "has_one" } #{options*', '} "
181
+ if other_method_name
182
+ line += " "*(48-line.length) if line.length < 48
183
+ line += "\# See #{role_player.name.gsub(/ /,'')}.#{other_method_name}"
184
+ end
166
185
  puts line
167
186
  #puts " \# REVISIT: #{other_role_name} has values restricted to #{role.role_value_constraint}\n" if role.role_value_constraint
168
187
  end
@@ -178,3 +197,5 @@ module ActiveFacts
178
197
  end
179
198
  end
180
199
  end
200
+
201
+ ActiveFacts::Registry.generator('ruby', ActiveFacts::Generate::RUBY)
@@ -9,7 +9,7 @@ require 'activefacts/persistence'
9
9
 
10
10
  module ActiveFacts
11
11
  module Generate
12
- class SQL #:nodoc:
12
+ module SQL #:nodoc:
13
13
  # Generate SQL for MySQL for an ActiveFacts vocabulary.
14
14
  # Invoke as
15
15
  # afgen --sql/mysql[=options] <file>.cql
@@ -165,7 +165,7 @@ module ActiveFacts
165
165
  puts "CREATE TABLE #{escape table.name.gsub(' ','')} ("
166
166
 
167
167
  pk = table.identifier_columns
168
- identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned
168
+ identity_column = pk[0] if pk[0].is_auto_assigned
169
169
 
170
170
  fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
171
171
  fk_columns = table.columns.select do |column|
@@ -245,7 +245,7 @@ module ActiveFacts
245
245
 
246
246
  private
247
247
  def sql_value(value)
248
- value.is_a_string ? sql_string(value.literal) : value.literal
248
+ value.is_literal_string ? sql_string(value.literal) : value.literal
249
249
  end
250
250
 
251
251
  def sql_string(str)
@@ -276,3 +276,5 @@ module ActiveFacts
276
276
  end
277
277
  end
278
278
  end
279
+
280
+ ActiveFacts::Registry.generator('sql/mysql', ActiveFacts::Generate::SQL::MYSQL)
@@ -9,7 +9,7 @@ require 'activefacts/persistence'
9
9
 
10
10
  module ActiveFacts
11
11
  module Generate
12
- class SQL #:nodoc:
12
+ module SQL #:nodoc:
13
13
  # Generate SQL for SQL Server for an ActiveFacts vocabulary.
14
14
  # Invoke as
15
15
  # afgen --sql/server[=options] <file>.cql
@@ -84,7 +84,7 @@ module ActiveFacts
84
84
  when length <= 8
85
85
  'tinyint'
86
86
  when length <= 16
87
- 'shortint'
87
+ 'smallint'
88
88
  when length <= 32
89
89
  'int'
90
90
  else
@@ -112,6 +112,8 @@ module ActiveFacts
112
112
  when /^Auto ?Time ?Stamp$/
113
113
  'timestamp'
114
114
 
115
+ when /^Guid$/
116
+ 'uniqueidentifier'
115
117
  when /^Money$/
116
118
  'decimal'
117
119
  when /^Picture ?Raw ?Data$/, /^Image$/
@@ -137,7 +139,7 @@ module ActiveFacts
137
139
  puts "CREATE TABLE #{escape table.name.gsub(' ',@underscore)} ("
138
140
 
139
141
  pk = table.identifier_columns
140
- identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned
142
+ identity_column = pk[0] if pk[0].is_auto_assigned
141
143
 
142
144
  fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
143
145
  fk_columns = table.columns.select do |column|
@@ -235,7 +237,7 @@ CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.c
235
237
 
236
238
  private
237
239
  def sql_value(value)
238
- value.is_a_string ? sql_string(value.literal) : value.literal
240
+ value.is_literal_string ? sql_string(value.literal) : value.literal
239
241
  end
240
242
 
241
243
  def sql_string(str)
@@ -266,3 +268,5 @@ CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.c
266
268
  end
267
269
  end
268
270
  end
271
+
272
+ ActiveFacts::Registry.generator('sql/server', ActiveFacts::Generate::SQL::SERVER)
@@ -24,3 +24,4 @@ module ActiveFacts
24
24
  end
25
25
  end
26
26
 
27
+ ActiveFacts::Registry.generator('text', ActiveFacts::Generate::TEXT)
@@ -0,0 +1,209 @@
1
+ #
2
+ # ActiveFacts Schema Transform
3
+ # Transform a loaded ActiveFacts vocabulary to suit ActiveRecord
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ require 'activefacts/vocabulary'
8
+ require 'activefacts/persistence'
9
+
10
+ module ActiveFacts
11
+ module Metamodel
12
+ class ObjectType
13
+
14
+ def add_surrogate type_name = 'Auto Counter', suffix = 'ID'
15
+ # Find or assert the surrogate value type
16
+ auto_counter = vocabulary.valid_value_type_name(type_name) ||
17
+ constellation.ValueType(:vocabulary => vocabulary, :name => type_name, :guid => :new)
18
+
19
+ # Create a subtype to identify this entity type:
20
+ vt_name = self.name + ' '+suffix
21
+ my_id = @vocabulary.valid_value_type_name(vt_name) ||
22
+ constellation.ValueType(:vocabulary => vocabulary, :name => vt_name, :guid => :new, :supertype => auto_counter)
23
+
24
+ # Create a fact type
25
+ identifying_fact_type = constellation.FactType(:guid => :new)
26
+ my_role = constellation.Role(:guid => :new, :fact_type => identifying_fact_type, :ordinal => 0, :object_type => self)
27
+ @injected_surrogate_role = my_role
28
+ id_role = constellation.Role(:guid => :new, :fact_type => identifying_fact_type, :ordinal => 1, :object_type => my_id)
29
+
30
+ # Create a reading (which needs a RoleSequence)
31
+ reading = constellation.Reading(
32
+ :fact_type => identifying_fact_type,
33
+ :ordinal => 0,
34
+ :role_sequence => [:new],
35
+ :text => "{0} has {1}"
36
+ )
37
+ constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 0, :role => my_role)
38
+ constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 1, :role => id_role)
39
+
40
+ # Create two uniqueness constraints for the one-to-one. Each needs a RoleSequence (two RoleRefs)
41
+ one_id = constellation.PresenceConstraint(
42
+ :guid => :new,
43
+ :vocabulary => vocabulary,
44
+ :name => self.name+'HasOne'+suffix,
45
+ :role_sequence => [:new],
46
+ :is_mandatory => true,
47
+ :min_frequency => 1,
48
+ :max_frequency => 1,
49
+ :is_preferred_identifier => false
50
+ )
51
+ @constellation.RoleRef(:role_sequence => one_id.role_sequence, :ordinal => 0, :role => my_role)
52
+
53
+ one_me = constellation.PresenceConstraint(
54
+ :guid => :new,
55
+ :vocabulary => vocabulary,
56
+ :name => self.name+suffix+'IsOfOne'+self.name,
57
+ :role_sequence => [:new],
58
+ :is_mandatory => false,
59
+ :min_frequency => 0,
60
+ :max_frequency => 1,
61
+ :is_preferred_identifier => true
62
+ )
63
+ @constellation.RoleRef(:role_sequence => one_me.role_sequence, :ordinal => 0, :role => id_role)
64
+ end
65
+ end
66
+
67
+ class ValueType
68
+ def needs_surrogate
69
+ supertype_names = supertypes_transitive.map(&:name)
70
+ !(supertype_names.include?('Auto Counter') or supertype_names.include?('Guid') or supertype_names.include?('ID'))
71
+ end
72
+
73
+ def inject_surrogate
74
+ debug :transform_surrogate, "Adding surrogate ID to Value Type #{name}"
75
+ add_surrogate('Auto Counter', 'ID')
76
+ end
77
+ end
78
+
79
+ class EntityType
80
+ def identifying_refs_from
81
+ pi = preferred_identifier
82
+ rrs = pi.role_sequence.all_role_ref
83
+
84
+ # REVISIT: This is actually a ref to us, not from
85
+ # if absorbed_via
86
+ # return [absorbed_via]
87
+ # end
88
+
89
+ rrs.map do |rr|
90
+ r = references_from.detect{|ref| rr.role == ref.to_role }
91
+ raise "failed to find #{name} identifying reference for #{rr.role.object_type.name} in #{references_from.inspect}" unless r
92
+ r
93
+ end
94
+ end
95
+
96
+ def needs_surrogate
97
+
98
+ # A recursive proc to replace any reference to an Entity Type by its identifying references:
99
+ debug :transform_surrogate_expansion, "Expanding key for #{name}"
100
+ substitute_identifying_refs = proc do |object|
101
+ if ref = object.absorbed_via
102
+ # This shouldn't be necessary, but see the absorbed_via comment above.
103
+ absorbed_into = ref.from
104
+ debug :transform_surrogate_expansion, "recursing to handle absorption of #{object.name} into #{absorbed_into.name}"
105
+ [substitute_identifying_refs.call(absorbed_into)]
106
+ else
107
+ irf = object.identifying_refs_from
108
+ debug :transform_surrogate_expansion, "Iterating for #{object.name} over #{irf.inspect}" do
109
+ irf.each_with_index do |ref, i|
110
+ next if ref.is_unary
111
+ next if ref.to_role.object_type.kind_of?(ActiveFacts::Metamodel::ValueType)
112
+ recurse_to = ref.to_role.object_type
113
+
114
+ debug :transform_surrogate_expansion, "#{i}: recursing to expand #{recurse_to.name} key in #{ref}" do
115
+ irf[i] = substitute_identifying_refs.call(recurse_to)
116
+ end
117
+ end
118
+ end
119
+ irf
120
+ end
121
+ end
122
+ irf = substitute_identifying_refs.call(self)
123
+
124
+ debug :transform_surrogate, "Does #{name} need a surrogate? it's identified by #{irf.inspect}" do
125
+
126
+ pk_fks = identifying_refs_from.map do |ref|
127
+ ref.to && ref.to.is_table ? ref.to : nil
128
+ end
129
+
130
+ irf.flatten!
131
+
132
+ # Multi-part identifiers are only allowed if each part is a foreign key (i.e. it's a join table) and the object is not the target of a foreign key:
133
+ if irf.size >= 2
134
+ if pk_fks.include?(nil)
135
+ debug :transform_surrogate, "#{self.name} needs a surrogate because its multi-part key contains a non-table"
136
+ return true
137
+ elsif references_to.size != 0
138
+ debug :transform_surrogate, "#{self.name} is a join table between #{pk_fks.map(&:name).inspect} but is also an FK target"
139
+ return true
140
+ else
141
+ debug :transform_surrogate, "#{self.name} is a join table between #{pk_fks.map(&:name).inspect}"
142
+ return false
143
+ end
144
+ return true
145
+ end
146
+
147
+ # Single-part key. It must be an Auto Counter, or we will add a surrogate
148
+
149
+ identifying_type = irf[0].to
150
+ if identifying_type.needs_surrogate
151
+ debug :transform_surrogate, "#{self.name} needs a surrogate because #{irf[0].to.name} is not an AutoCounter, but #{identifying_type.supertypes_transitive.map(&:name).inspect}"
152
+ return true
153
+ end
154
+
155
+ false
156
+ end
157
+ end
158
+
159
+ def inject_surrogate
160
+ debug :transform_surrogate, "Injecting a surrogate key into #{self.name}"
161
+
162
+ # Disable the preferred identifier:
163
+ pi = preferred_identifier
164
+ debug :transform_surrogate, "pi for #{name} was '#{pi.describe}'"
165
+ pi.is_preferred_identifier = false
166
+ @preferred_identifier = nil # Kill the cache
167
+
168
+ add_surrogate
169
+
170
+ debug :transform_surrogate, "pi for #{name} is now '#{preferred_identifier.describe}'"
171
+ end
172
+
173
+ end
174
+ end
175
+
176
+ module Persistence
177
+ class Column
178
+ def is_injected_surrogate
179
+ references.size == 1 and
180
+ references[0].from_role == references[0].from.injected_surrogate_role
181
+ end
182
+ end
183
+ end
184
+
185
+ module Generate #:nodoc:
186
+ module Transform #:nodoc:
187
+ class Surrogate
188
+ def initialize(vocabulary, *options)
189
+ @vocabulary = vocabulary
190
+ end
191
+
192
+ def generate(out = $stdout)
193
+ @out = out
194
+ injections =
195
+ @vocabulary.tables.select do |table|
196
+ table.needs_surrogate
197
+ end
198
+ injections.each do |table|
199
+ table.inject_surrogate
200
+ end
201
+
202
+ @vocabulary.decide_tables
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ ActiveFacts::Registry.generator('transform/surrogate', ActiveFacts::Generate::Transform::Surrogate)