activefacts-compositions 1.9.8 → 1.9.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,7 +6,6 @@
6
6
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
7
7
  #
8
8
  require 'activefacts/metamodel'
9
- require 'activefacts/registry'
10
9
  require 'activefacts/compositions'
11
10
  require 'activefacts/generator'
12
11
  require 'byebug'
@@ -25,7 +24,7 @@ module ActiveFacts
25
24
 
26
25
  # Base class for generators of object-oriented class libraries for an ActiveFacts vocabulary.
27
26
  def initialize vocabulary, options = {}
28
- @vocabulary = vocabulary
27
+ @vocabulary = vocabulary # REVISIT: This should be a Composition here
29
28
  @options = options
30
29
  @gen_bootstrap = options.has_key?("gen_bootstrap")
31
30
  end
@@ -4,7 +4,6 @@
4
4
  # Copyright (c) 2009-2016 Clifford Heath. Read the LICENSE file.
5
5
  #
6
6
  require 'activefacts/metamodel'
7
- require 'activefacts/registry'
8
7
  require 'activefacts/compositions'
9
8
  require 'activefacts/generator'
10
9
 
@@ -20,8 +19,9 @@ module ActiveFacts
20
19
  }
21
20
  end
22
21
 
23
- def initialize composition, options = {}
24
- @composition = composition
22
+ def initialize compositions, options = {}
23
+ raise "--graphviz only processes a single composition" if compositions.size > 1
24
+ @composition = compositions[0]
25
25
  @options = options
26
26
  end
27
27
 
@@ -7,7 +7,6 @@
7
7
  #
8
8
  require 'digest/sha1'
9
9
  require 'activefacts/metamodel'
10
- require 'activefacts/registry'
11
10
  require 'activefacts/compositions'
12
11
  require 'activefacts/generator'
13
12
  require 'activefacts/support'
@@ -22,11 +21,12 @@ module ActiveFacts
22
21
  }
23
22
  end
24
23
 
25
- def initialize composition, options = {}
26
- @composition = composition
24
+ def initialize compositions, options = {}
25
+ raise "--ldm only processes a single composition" if compositions.size > 1
26
+ @composition = compositions[0]
27
27
  @options = options
28
28
  @underscore = options.has_key?("underscore") ? (options['underscore'] || '_') : ''
29
-
29
+
30
30
  @vocabulary = composition.constellation.Vocabulary.values[0] # REVISIT when importing from other vocabularies
31
31
  # glossary_options = {"gen_bootstrap" => true}
32
32
  # @glossary = GLOSSARY.new(@vocabulary, glossary_options)
@@ -34,7 +34,7 @@ module ActiveFacts
34
34
 
35
35
  def generate
36
36
  @tables_emitted = {}
37
-
37
+
38
38
  # trace.enable 'ldm'
39
39
 
40
40
  generate_header +
@@ -78,22 +78,22 @@ module ActiveFacts
78
78
 
79
79
  def generate_header
80
80
  css_file = "/css/ldm.css"
81
-
81
+
82
82
  "<!DOCTYPE html>\n" +
83
- "<html lang=\"en\">\n" +
83
+ "<html lang=\"en\">\n" +
84
84
  " <head>\n" +
85
85
  " <meta charset=\"utf-8\">\n" +
86
86
  " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n" +
87
87
  " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n" +
88
- " <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->\n" +
89
- " <title>Logical Data Model for " + @composition.name + "</title>\n" +
88
+ " <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->\n" +
89
+ " <title>Logical Data Model for " + @composition.name + "</title>\n" +
90
90
  "\n" +
91
91
  " <!-- Bootstrap -->\n" +
92
92
  " <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css\" integrity=\"sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7\" crossorigin=\"anonymous\">\n" +
93
93
  "\n" +
94
94
  " <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->\n" +
95
95
  " <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->\n" +
96
- " <!--[if lt IE 9]>\n" +
96
+ " <!--[if lt IE 9]>\n" +
97
97
  " <script src=\"https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js\"></script>\n" +
98
98
  " <script src=\"https://oss.maxcdn.com/respond/1.4.2/respond.min.js\"></script>\n" +
99
99
  " <![endif]-->\n" +
@@ -118,49 +118,49 @@ module ActiveFacts
118
118
  reject {|c| c.mapping.object_type.is_static}.
119
119
  reject {|c| c.mapping.object_type.fact_type}.
120
120
  map {|c| c.mapping.object_type}
121
-
121
+
122
122
  @definitions = {}
123
123
  defns.each do |o|
124
124
  @definitions[o] = true
125
125
  end
126
-
127
- defns.each do |o|
126
+
127
+ defns.each do |o|
128
128
  ftm = relevant_fact_types(o)
129
-
129
+
130
130
  trace :ldm, "expanding #{o.name}"
131
-
131
+
132
132
  ftm.each do |r, ft|
133
133
  next if ft.is_a?(ActiveFacts::Metamodel::TypeInheritance)
134
134
  ft.all_role.each do |ftr|
135
135
  next if @definitions[ftr.object_type]
136
136
  next if ftr.object_type.is_a?(ActiveFacts::Metamodel::ValueType)
137
-
137
+
138
138
  trace :ldm, "adding #{ftr.object_type.name}"
139
-
139
+
140
140
  defns = defns << ftr.object_type
141
141
  @definitions[ftr.object_type] = true
142
142
  end
143
143
  end
144
144
  end
145
-
145
+
146
146
  " <h2>Business Definitions and Relationships</h2>\n" +
147
147
  defns.sort_by{|o| o.name.gsub(/ /, '').downcase}.map do |o|
148
148
  entity_type_dump(o, 0)
149
149
  end * "\n" + "\n"
150
- end
151
-
150
+ end
151
+
152
152
  def generate_diagrams
153
153
  ''
154
154
  end
155
-
156
- def generate_details
155
+
156
+ def generate_details
157
157
  h2("Logical Data Model Details") +
158
158
  @composition.
159
159
  all_composite.
160
160
  sort_by{|composite| composite.mapping.name}.
161
161
  map{|composite| generate_table(composite)}*"\n" + "\n"
162
162
  end
163
-
163
+
164
164
  def generate_footer
165
165
  " </div>\n" +
166
166
  " </div>\n" +
@@ -224,7 +224,7 @@ module ActiveFacts
224
224
  def term(name)
225
225
  element(name, :class=>:object_type)
226
226
  end
227
-
227
+
228
228
  #
229
229
  # Dump functions
230
230
  #
@@ -239,10 +239,10 @@ module ActiveFacts
239
239
 
240
240
  cn_array = o.concept.all_context_note_as_relevant_concept.map{|cn| [cn.context_note_kind, cn.discussion] }
241
241
  cn_hash = cn_array.inject({}) do |hash, value|
242
- hash[value.first] = value.last
242
+ hash[value.first] = value.last
243
243
  hash
244
244
  end
245
-
245
+
246
246
  informal_defn = cn_hash["because"]
247
247
  defn_term =
248
248
  " <div class=\"row\">\n" +
@@ -254,7 +254,7 @@ module ActiveFacts
254
254
  defn_detail =
255
255
  " <div class=\"row\">\n" +
256
256
  " <div class=\"col-md-12 details\">\n" +
257
- (supers.size > 0 ?
257
+ (supers.size > 0 ?
258
258
  "#{span('Each', 'keyword')} #{termref(o.name, nil, o)} #{span('is a kind of', 'keyword')} #{supers.map{|s| termref(s.name, nil, s)}*', '}\n" :
259
259
  ''
260
260
  ) +
@@ -276,7 +276,7 @@ module ActiveFacts
276
276
  fact_types_dump(o, relevant_fact_types(o)) + "\n" +
277
277
  " </div>\n" +
278
278
  " </div>\n"
279
-
279
+
280
280
  defn_term + defn_detail
281
281
  end
282
282
 
@@ -287,7 +287,7 @@ module ActiveFacts
287
287
  reject { |r, ft| ft.is_a?(ActiveFacts::Metamodel::LinkFactType) }.
288
288
  select { |r, ft| ft.entity_type || has_another_nonstatic_role(ft, r) }
289
289
  end
290
-
290
+
291
291
  def has_another_nonstatic_role(ft, r)
292
292
  ft.all_role.detect do |rr|
293
293
  rr != r &&
@@ -295,7 +295,7 @@ module ActiveFacts
295
295
  !rr.object_type.is_static
296
296
  end
297
297
  end
298
-
298
+
299
299
  def fact_types_dump(o, ftm)
300
300
  ftm.
301
301
  map { |r, ft| [ft, " #{fact_type_dump(ft, o)}"] }.
@@ -314,11 +314,11 @@ module ActiveFacts
314
314
  fact_type_block(ft, wrt)
315
315
  end
316
316
  end
317
-
317
+
318
318
  def fact_type_block(ft, wrt = nil, include_rolenames = true)
319
319
  div(expand_fact_type(ft, wrt, include_rolenames, ''), 'glossary-facttype')
320
320
  end
321
-
321
+
322
322
  def expand_fact_type(ft, wrt = nil, include_rolenames = true, wrt_qualifier = '')
323
323
  role = ft.all_role.detect{|r| r.object_type == wrt}
324
324
  preferred_reading = ft.reading_preferably_starting_with_role(role)
@@ -329,7 +329,7 @@ module ActiveFacts
329
329
  'glossary-reading'
330
330
  )
331
331
  end
332
-
332
+
333
333
  def role_ref(rr, freq_con, l_adj, name, t_adj, role_name_def, literal)
334
334
  term_parts = [l_adj, termref(name, nil, rr.role.object_type), t_adj].compact
335
335
  [
@@ -363,7 +363,7 @@ module ActiveFacts
363
363
  {:class => 'reading'}
364
364
  )
365
365
  end
366
-
366
+
367
367
  def generate_table(composite)
368
368
  @tables_emitted[composite] = true
369
369
  delayed_indices = []
@@ -387,7 +387,7 @@ module ActiveFacts
387
387
  ).compact.flat_map{|f| "#{f}" }*"\n"+"\n" +
388
388
  " </tbody>\n" +
389
389
  " </table>\n"
390
-
390
+
391
391
  table_keys =
392
392
  (
393
393
  composite.all_index.map do |index|
@@ -399,7 +399,7 @@ module ActiveFacts
399
399
  end.compact.sort
400
400
  ).compact.flat_map{|f| "#{f}" }*"<br>\n"+"\n"
401
401
 
402
- table_values =
402
+ table_values =
403
403
  if composite.mapping.object_type.all_instance.size > 0 then
404
404
  table_values =
405
405
  " <table class=\"table table-bordered table-striped\">\n" +
@@ -421,7 +421,7 @@ module ActiveFacts
421
421
  else
422
422
  ''
423
423
  end
424
-
424
+
425
425
  table_defn + table_keys + table_values
426
426
  end
427
427
 
@@ -431,13 +431,13 @@ module ActiveFacts
431
431
  constraints = leaf.all_leaf_constraint
432
432
 
433
433
  identity = ''
434
-
434
+
435
435
  " " * indent + "<tr>\n" +
436
436
  " " * indent + " <td>#{column_name}\n" +
437
437
  " " * indent + " <td>#{component_type(leaf, column_name)}\n" +
438
438
  " " * indent + " <td>#{leaf.path_mandatory ? 'Yes' : 'No'}\n" +
439
439
  " " * indent + " <td>#{column_comment leaf}\n" +
440
- " " * indent + "</tr>"
440
+ " " * indent + "</tr>"
441
441
  # "-- #{column_comment leaf}\n\t#{column_name}#{padding}#{component_type leaf, column_name}#{identity}"
442
442
  end
443
443
 
@@ -5,7 +5,6 @@
5
5
  #
6
6
  require 'digest/sha1'
7
7
  require 'activefacts/metamodel'
8
- require 'activefacts/registry'
9
8
  require 'activefacts/compositions'
10
9
  require 'activefacts/generator'
11
10
 
@@ -19,8 +18,9 @@ module ActiveFacts
19
18
  }
20
19
  end
21
20
 
22
- def initialize composition, options = {}
23
- @composition = composition
21
+ def initialize compositions, options = {}
22
+ raise "--oo only processes a single composition" if compositions.size > 1
23
+ @composition = compositions[0]
24
24
  @options = options
25
25
  @comments = @options.delete("comments")
26
26
  end
@@ -5,7 +5,6 @@
5
5
  #
6
6
  require 'digest/sha1'
7
7
  require 'activefacts/metamodel'
8
- require 'activefacts/registry'
9
8
  require 'activefacts/compositions'
10
9
  require 'activefacts/generator'
11
10
  require 'activefacts/compositions/traits/rails'
@@ -23,8 +22,9 @@ module ActiveFacts
23
22
  })
24
23
  end
25
24
 
26
- def initialize composition, options = {}
27
- @composition = composition
25
+ def initialize compositions, options = {}
26
+ raise "--rails/models only processes a single composition" if compositions.size > 1
27
+ @composition = compositions[0]
28
28
  @options = options
29
29
  @option_output = options.delete("output")
30
30
  @option_concern = options.delete("concern")
@@ -221,7 +221,7 @@ module ActiveFacts
221
221
  return [] unless @option_validations
222
222
  ccs =
223
223
  composite.mapping.all_leaf.flat_map do |component|
224
- next unless component.path_mandatory
224
+ next unless component.path_mandatory && !component.is_a?(Metamodel::Indicator)
225
225
  next if component.is_a?(Metamodel::Mapping) && component.object_type.is_a?(Metamodel::ValueType) && component.is_auto_assigned
226
226
  [ " validates :#{component.column_name.snakecase}, :presence => true" ]
227
227
  end.compact
@@ -6,7 +6,6 @@
6
6
  require 'digest/sha1'
7
7
  require 'activefacts/metamodel'
8
8
  require 'activefacts/metamodel/datatypes'
9
- require 'activefacts/registry'
10
9
  require 'activefacts/compositions'
11
10
  require 'activefacts/generator'
12
11
  require 'activefacts/compositions/traits/rails'
@@ -25,8 +24,9 @@ module ActiveFacts
25
24
  })
26
25
  end
27
26
 
28
- def initialize composition, options = {}
29
- @composition = composition
27
+ def initialize compositions, options = {}
28
+ raise "--rails/schema only processes a single composition" if compositions.size > 1
29
+ @composition = compositions[0]
30
30
  @options = options
31
31
  @option_exclude_fks = options.delete("exclude_fks")
32
32
  @option_include_comments = options.delete("include_comments")
@@ -42,6 +42,7 @@ module ActiveFacts
42
42
  end
43
43
 
44
44
  def generate
45
+ @indexes_generated = {}
45
46
  @foreign_keys = []
46
47
  # If we get index names that need to be truncated, add a counter to ensure uniqueness
47
48
  @dup_id = 0
@@ -105,6 +106,23 @@ module ActiveFacts
105
106
  create_table = %Q{ create_table "#{ar_table_name}", id: false, force: true do |t|}
106
107
  columns = generate_columns composite
107
108
 
109
+ index_texts = []
110
+ composite.all_index.each do |index|
111
+ next if index.composite_as_primary_index && index.all_index_field.size == 1 # We've handled this already
112
+
113
+ index_column_names = index.all_index_field.map{|ixf| ixf.component.column_name.snakecase}
114
+ index_name = ACTR::name_trunc("index_#{ar_table_name}_on_#{index_column_names*'_'}")
115
+
116
+ index_texts << '' if index_texts.empty?
117
+
118
+ all_mandatory = index.all_index_field.to_a.all?{|ixf| ixf.component.path_mandatory}
119
+ @indexes_generated[index] = true
120
+ index_texts << %Q{ add_index "#{ar_table_name}", #{index_column_names.inspect}, name: :#{index_name}#{
121
+ # Avoid problems with closed-world uniqueness: only all_mandatory indices can be unique on closed-world index semantics (MS SQL)
122
+ index.is_unique && (!@option_closed_world || all_mandatory) ? ", unique: true" : ''
123
+ }}
124
+ end
125
+
108
126
  unless @option_exclude_fks
109
127
  composite.all_foreign_key_as_source_composite.each do |fk|
110
128
  from_column_names = fk.all_foreign_key_field.map{|fxf| fxf.component.column_name.snakecase}
@@ -112,12 +130,30 @@ module ActiveFacts
112
130
 
113
131
  @foreign_keys.concat(
114
132
  if (from_column_names.length == 1)
133
+
134
+ # See if the from_column already has an index (not necessarily unique, but the column must be first):
135
+ from_index_needed =
136
+ fk.
137
+ all_foreign_key_field.
138
+ single.
139
+ component. # See whether the foreign key component
140
+ all_index_field. # occurs in a unique index already
141
+ select do |ixf|
142
+ ixf.access_path.is_a?(Metamodel::Index) && # It's an Index, not an FK
143
+ ixf.ordinal == 0 # It's first in this index
144
+ end
145
+ already_indexed = from_index_needed.any?{|ixf| @indexes_generated[ixf.access_path]}
146
+ is_one_to_one = fk.absorption && fk.absorption.child_role.is_unique
147
+
115
148
  index_name = ACTR::name_trunc("index_#{ar_table_name}_on_#{from_column_names[0]}")
116
149
  [
117
150
  " add_foreign_key :#{ar_table_name}, :#{fk.composite.mapping.rails.plural_name}, column: :#{from_column_names[0]}, primary_key: :#{to_column_names[0]}, on_delete: :cascade",
118
151
  # Index it non-uniquely only if it's not unique already:
119
- fk.absorption && fk.absorption.child_role.is_unique ? nil :
152
+ if already_indexed or is_one_to_one # Either it already is, or it will be indexed, no new index needed
153
+ nil
154
+ else
120
155
  " add_index :#{ar_table_name}, [:#{from_column_names[0]}], unique: false, name: :#{index_name}"
156
+ end
121
157
  ].compact
122
158
  else
123
159
  [ ]
@@ -126,22 +162,6 @@ module ActiveFacts
126
162
  end
127
163
  end
128
164
 
129
- index_texts = []
130
- composite.all_index.each do |index|
131
- next if index.composite_as_primary_index && index.all_index_field.size == 1 # We've handled this already
132
-
133
- index_column_names = index.all_index_field.map{|ixf| ixf.component.column_name.snakecase}
134
- index_name = ACTR::name_trunc("index_#{ar_table_name}_on_#{index_column_names*'_'}")
135
-
136
- index_texts << '' if index_texts.empty?
137
-
138
- all_mandatory = index.all_index_field.to_a.all?{|ixf| ixf.component.path_mandatory}
139
- index_texts << %Q{ add_index "#{ar_table_name}", #{index_column_names.inspect}, name: :#{index_name}#{
140
- # Avoid problems with closed-world uniqueness: only all_mandatory indices can be unique on closed-world index semantics (MS SQL)
141
- index.is_unique && (!@option_closed_world || all_mandatory) ? ", unique: true" : ''
142
- }}
143
- end
144
-
145
165
  [
146
166
  create_table,
147
167
  *columns,
@@ -165,12 +185,18 @@ module ActiveFacts
165
185
  value_constraint = options[:value_constraint]
166
186
  type, type_name = *normalise_type(type_name)
167
187
 
168
- if a = options[:auto_assign]
188
+ if pkxf = component.all_index_field.detect{|ixf| (a = ixf.access_path).is_a?(MM::Index) && a.composite_as_primary_index }
189
+ auto_assign = options[:auto_assign]
169
190
  case type_name
170
191
  when 'integer'
171
- type_name = 'primary_key' if a != 'assert'
192
+ type_name = 'primary_key' if auto_assign
193
+ @indexes_generated[pkxf.access_path] = true
172
194
  when 'uuid'
173
- type_name = "uuid, default: 'gen_random_uuid()', primary_key: true"
195
+ type_name = "uuid"
196
+ if auto_assign
197
+ type_name += ", default: 'gen_random_uuid()', primary_key: true"
198
+ @indexes_generated[pkxf.access_path] = true
199
+ end
174
200
  end
175
201
  end
176
202
 
@@ -250,7 +276,7 @@ module ActiveFacts
250
276
  when MM::DataType::TYPE_Integer; 'integer'
251
277
  when MM::DataType::TYPE_Real; 'float'
252
278
  when MM::DataType::TYPE_Decimal; 'decimal'
253
- when MM::DataType::TYPE_Money; 'datatime'
279
+ when MM::DataType::TYPE_Money; 'decimal'
254
280
  when MM::DataType::TYPE_Char; 'string'
255
281
  when MM::DataType::TYPE_String; 'string'
256
282
  when MM::DataType::TYPE_Text; 'text'
@@ -258,7 +284,12 @@ module ActiveFacts
258
284
  when MM::DataType::TYPE_Time; 'time'
259
285
  when MM::DataType::TYPE_DateTime; 'datetime'
260
286
  when MM::DataType::TYPE_Timestamp;'datetime'
261
- when MM::DataType::TYPE_Binary; 'binary'
287
+ when MM::DataType::TYPE_Binary;
288
+ if type_name =~ /^([Gu]uid|uniqueidentifier)$/i
289
+ 'uuid'
290
+ else
291
+ 'binary'
292
+ end
262
293
  else
263
294
  type_name
264
295
  end