activefacts 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/README.rdoc +0 -3
  2. data/Rakefile +7 -5
  3. data/bin/afgen +5 -2
  4. data/bin/cql +3 -2
  5. data/examples/CQL/Genealogy.cql +3 -3
  6. data/examples/CQL/Metamodel.cql +10 -7
  7. data/examples/CQL/MultiInheritance.cql +2 -1
  8. data/examples/CQL/OilSupply.cql +4 -4
  9. data/examples/CQL/Orienteering.cql +2 -2
  10. data/lib/activefacts.rb +2 -1
  11. data/lib/activefacts/api.rb +21 -2
  12. data/lib/activefacts/api/concept.rb +52 -39
  13. data/lib/activefacts/api/constellation.rb +8 -6
  14. data/lib/activefacts/api/entity.rb +41 -37
  15. data/lib/activefacts/api/instance.rb +5 -3
  16. data/lib/activefacts/api/numeric.rb +28 -21
  17. data/lib/activefacts/api/role.rb +29 -43
  18. data/lib/activefacts/api/standard_types.rb +8 -3
  19. data/lib/activefacts/api/support.rb +4 -4
  20. data/lib/activefacts/api/value.rb +9 -3
  21. data/lib/activefacts/api/vocabulary.rb +17 -7
  22. data/lib/activefacts/cql.rb +10 -7
  23. data/lib/activefacts/cql/CQLParser.treetop +6 -0
  24. data/lib/activefacts/cql/Concepts.treetop +32 -26
  25. data/lib/activefacts/cql/DataTypes.treetop +6 -0
  26. data/lib/activefacts/cql/Expressions.treetop +6 -0
  27. data/lib/activefacts/cql/FactTypes.treetop +6 -0
  28. data/lib/activefacts/cql/Language/English.treetop +9 -3
  29. data/lib/activefacts/cql/LexicalRules.treetop +6 -0
  30. data/lib/activefacts/cql/Rakefile +8 -0
  31. data/lib/activefacts/cql/parser.rb +4 -2
  32. data/lib/activefacts/generate/absorption.rb +20 -28
  33. data/lib/activefacts/generate/cql.rb +28 -16
  34. data/lib/activefacts/generate/cql/html.rb +327 -321
  35. data/lib/activefacts/generate/null.rb +7 -3
  36. data/lib/activefacts/generate/oo.rb +19 -15
  37. data/lib/activefacts/generate/ordered.rb +457 -461
  38. data/lib/activefacts/generate/ruby.rb +12 -4
  39. data/lib/activefacts/generate/sql/server.rb +42 -10
  40. data/lib/activefacts/generate/text.rb +7 -3
  41. data/lib/activefacts/input/cql.rb +55 -28
  42. data/lib/activefacts/input/orm.rb +32 -22
  43. data/lib/activefacts/persistence.rb +5 -0
  44. data/lib/activefacts/persistence/columns.rb +66 -32
  45. data/lib/activefacts/persistence/foreignkey.rb +29 -5
  46. data/lib/activefacts/persistence/index.rb +57 -25
  47. data/lib/activefacts/persistence/reference.rb +65 -30
  48. data/lib/activefacts/persistence/tables.rb +28 -17
  49. data/lib/activefacts/support.rb +8 -0
  50. data/lib/activefacts/version.rb +7 -1
  51. data/lib/activefacts/vocabulary.rb +4 -2
  52. data/lib/activefacts/vocabulary/extensions.rb +12 -10
  53. data/lib/activefacts/vocabulary/metamodel.rb +24 -23
  54. data/spec/api/autocounter.rb +2 -2
  55. data/spec/api/entity_type.rb +2 -2
  56. data/spec/api/instance.rb +61 -30
  57. data/spec/api/roles.rb +9 -9
  58. data/spec/cql_parse_spec.rb +1 -0
  59. data/spec/norma_tables_spec.rb +3 -3
  60. metadata +8 -4
@@ -1,3 +1,8 @@
1
+ #
2
+ # ActiveFacts Relational mapping and persistence.
3
+ #
4
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
5
+ #
1
6
  require 'activefacts/persistence/reference'
2
7
  require 'activefacts/persistence/tables'
3
8
  require 'activefacts/persistence/columns'
@@ -1,4 +1,9 @@
1
1
  #
2
+ # ActiveFacts Relational mapping and persistence.
3
+ # Columns in a relational table; each is derived from a sequence of References.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
2
7
  # Each Reference from a Concept creates one or more Columns.
3
8
  # A reference to a simple valuetype creates a single column, as
4
9
  # does a reference to a table entity identified by a single value.
@@ -10,15 +15,18 @@
10
15
  # table, a reference to that entity creates multiple columns,
11
16
  # a multi-part foreign key.
12
17
  #
13
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
14
- #
15
18
 
16
19
  module ActiveFacts
17
- module Metamodel
20
+ module Persistence #:nodoc:
18
21
 
19
22
  class Column
20
- attr_reader :references
23
+ include Metamodel
24
+
25
+ def initialize(reference = nil) #:nodoc:
26
+ references << reference if reference
27
+ end
21
28
 
29
+ # A Column is created from a path through an array of References to a ValueType
22
30
  def references
23
31
  @references ||= []
24
32
  end
@@ -33,6 +41,7 @@ module ActiveFacts
33
41
  end
34
42
  end
35
43
 
44
+ # How many of the initial references are involved in full absorption of an EntityType into this column's table
36
45
  def absorption_level
37
46
  l = 0
38
47
  @references.detect do |ref|
@@ -42,15 +51,13 @@ module ActiveFacts
42
51
  l
43
52
  end
44
53
 
45
- def initialize(reference = nil)
46
- references << reference if reference
47
- end
48
-
49
- def prepend reference
54
+ def prepend reference #:nodoc:
50
55
  references.insert 0, reference
51
56
  self
52
57
  end
53
58
 
59
+ # A Column name is a sequence of names (derived from the to_roles of the References)
60
+ # joined by a joiner string (pass nil to get the original array of names)
54
61
  def name(joiner = "")
55
62
  last_name = ""
56
63
  names = @references.
@@ -61,7 +68,7 @@ module ActiveFacts
61
68
  ref.to and
62
69
  ref.to.is_a?(EntityType) and
63
70
  (role_refs = ref.to.preferred_identifier.role_sequence.all_role_ref).size == 1 and
64
- role_refs[0].role == ref.from_role
71
+ role_refs.only.role == ref.from_role
65
72
  end.
66
73
  inject([]) do |a, ref|
67
74
  names = ref.to_names
@@ -85,7 +92,7 @@ module ActiveFacts
85
92
  if names.size > 1 and
86
93
  (et = @references.last.from).is_a?(EntityType) and
87
94
  (role_refs = et.preferred_identifier.role_sequence.all_role_ref).size == 1 and
88
- role_refs[0].role == @references.last.to_role and
95
+ role_refs.only.role == @references.last.to_role and
89
96
  names.last[0...et.name.size].downcase == et.name.downcase
90
97
  names[-1] = names.last[et.name.size..-1]
91
98
  names.pop if names.last == ''
@@ -95,14 +102,17 @@ module ActiveFacts
95
102
  joiner ? name_array * joiner : name_array
96
103
  end
97
104
 
105
+ # Is this column mandatory or nullable?
98
106
  def is_mandatory
99
107
  !@references.detect{|ref| !ref.is_mandatory}
100
108
  end
101
109
 
110
+ # Is this column an auto-assigned value type?
102
111
  def is_auto_assigned
103
112
  (to = references[-1].to) && to.is_auto_assigned
104
113
  end
105
114
 
115
+ # What's the underlying SQL data type of this column?
106
116
  def type
107
117
  params = {}
108
118
  restrictions = []
@@ -126,6 +136,7 @@ module ActiveFacts
126
136
  return [vt.name, params, restrictions]
127
137
  end
128
138
 
139
+ # The comment is the readings from the References expressed as a join
129
140
  def comment
130
141
  @references.map do |ref|
131
142
  (ref.is_mandatory ? "" : "maybe ") +
@@ -134,13 +145,13 @@ module ActiveFacts
134
145
  end * " and "
135
146
  end
136
147
 
137
- def to_s
148
+ def to_s #:nodoc:
138
149
  "#{@references[0].from.name} column #{name('.')}"
139
150
  end
140
151
  end
141
152
 
142
153
  class Reference
143
- def columns(excluded_supertypes)
154
+ def columns(excluded_supertypes) #:nodoc:
144
155
  kind = ""
145
156
  cols =
146
157
  if is_unary
@@ -167,16 +178,24 @@ module ActiveFacts
167
178
  end
168
179
  end
169
180
  end
181
+ end
170
182
 
183
+ module Metamodel #:nodoc:
184
+ # The Concept class is defined in the metamodel; full documentation is not generated.
185
+ # This section shows the features relevant to relational Persistence.
171
186
  class Concept
172
- attr_accessor :columns
187
+ # The array of columns for this Concept's table
188
+ def columns; @columns; end
173
189
 
174
- def populate_columns
190
+ def populate_columns #:nodoc:
175
191
  @columns = all_columns({})
176
192
  end
177
193
  end
178
194
 
179
- class ValueType
195
+ # The ValueType class is defined in the metamodel; full documentation is not generated.
196
+ # This section shows the features relevant to relational Persistence.
197
+ class ValueType < Concept
198
+ # The identifier_columns for a ValueType can only ever be the self-value role that was injected
180
199
  def identifier_columns
181
200
  debug :columns, "Identifier Columns for #{name}" do
182
201
  raise "Illegal call to identifier_columns for absorbed ValueType #{name}" unless is_table
@@ -184,23 +203,27 @@ module ActiveFacts
184
203
  end
185
204
  end
186
205
 
187
- def reference_columns(excluded_supertypes)
206
+ # When creating a foreign key to this ValueType, what columns must we include?
207
+ # This must be a fresh copy, because the columns will have References prepended
208
+ def reference_columns(excluded_supertypes) #:nodoc:
188
209
  debug :columns, "Reference Columns for #{name}" do
189
210
  if is_table
190
- [Column.new(self_value_reference)]
211
+ [ActiveFacts::Persistence::Column.new(self_value_reference)]
191
212
  else
192
- [Column.new]
213
+ [ActiveFacts::Persistence::Column.new]
193
214
  end
194
215
  end
195
216
  end
196
217
 
197
- def all_columns(excluded_supertypes)
218
+ # When absorbing this ValueType, what columns must be absorbed?
219
+ # This must be a fresh copy, because the columns will have References prepended.
220
+ def all_columns(excluded_supertypes) #:nodoc:
198
221
  columns = []
199
222
  debug :columns, "All Columns for #{name}" do
200
223
  if is_table
201
224
  self_value_reference
202
225
  else
203
- columns << Column.new
226
+ columns << ActiveFacts::Persistence::Column.new
204
227
  end
205
228
  references_from.each do |ref|
206
229
  debug :columns, "Columns absorbed via #{ref}" do
@@ -211,19 +234,23 @@ module ActiveFacts
211
234
  columns
212
235
  end
213
236
 
214
- def self_value_reference
237
+ # If someone asks for this, it's because it's needed, so create it.
238
+ def self_value_reference #:nodoc:
215
239
  # Make a reference for the self-value column
216
- @self_value_reference ||= Reference.new(self, nil).tabulate
240
+ @self_value_reference ||= ActiveFacts::Persistence::Reference.new(self, nil).tabulate
217
241
  end
218
242
  end
219
243
 
220
- class EntityType
244
+ # The EntityType class is defined in the metamodel; full documentation is not generated.
245
+ # This section shows the features relevant to relational Persistence.
246
+ class EntityType < Concept
247
+ # The identifier_columns for an EntityType are the columns that result from the identifying roles
221
248
  def identifier_columns
222
249
  debug :columns, "Identifier Columns for #{name}" do
223
250
  if absorbed_via and
224
251
  # If this is a subtype that has its own identification, use that.
225
- (all_type_inheritance_by_subtype.size == 0 ||
226
- all_type_inheritance_by_subtype.detect{|ti| ti.provides_identification })
252
+ (all_type_inheritance_as_subtype.size == 0 ||
253
+ all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification })
227
254
  return absorbed_via.from.identifier_columns
228
255
  end
229
256
 
@@ -235,13 +262,15 @@ module ActiveFacts
235
262
  end
236
263
  end
237
264
 
238
- def reference_columns(excluded_supertypes)
265
+ # When creating a foreign key to this EntityType, what columns must we include (the identifier columns)?
266
+ # This must be a fresh copy, because the columns will have References prepended
267
+ def reference_columns(excluded_supertypes) #:nodoc:
239
268
  debug :columns, "Reference Columns for #{name}" do
240
269
 
241
270
  if absorbed_via and
242
271
  # If this is a subtype that has its own identification, use that.
243
- (all_type_inheritance_by_subtype.size == 0 ||
244
- all_type_inheritance_by_subtype.detect{|ti| ti.provides_identification })
272
+ (all_type_inheritance_as_subtype.size == 0 ||
273
+ all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification })
245
274
  return absorbed_via.from.reference_columns(excluded_supertypes)
246
275
  end
247
276
 
@@ -257,7 +286,9 @@ module ActiveFacts
257
286
  end
258
287
  end
259
288
 
260
- def all_columns(excluded_supertypes)
289
+ # When absorbing this EntityType, what columns must be absorbed?
290
+ # This must be a fresh copy, because the columns will have References prepended.
291
+ def all_columns(excluded_supertypes) #:nodoc:
261
292
  debug :columns, "All Columns for #{name}" do
262
293
  columns = []
263
294
  sups = supertypes
@@ -287,15 +318,18 @@ module ActiveFacts
287
318
  end
288
319
  end
289
320
 
321
+ # The Vocabulary class is defined in the metamodel; full documentation is not generated.
322
+ # This section shows the features relevant to relational Persistence.
290
323
  class Vocabulary
291
- # Do things like adding ID fields and ValueType self-value columns
324
+ # Make schema transformations like adding ValueType self-value columns (and later, Rails-friendly ID fields).
325
+ # Override this method to change the transformations
292
326
  def finish_schema
293
327
  all_feature.each do |feature|
294
328
  feature.self_value_reference if feature.is_a?(ValueType) && feature.is_table
295
329
  end
296
330
  end
297
331
 
298
- def populate_all_columns
332
+ def populate_all_columns #:nodoc:
299
333
  # REVISIT: Is now a good time to apply schema transforms or should this be more explicit?
300
334
  finish_schema
301
335
 
@@ -1,15 +1,38 @@
1
+ #
2
+ # ActiveFacts Relational mapping and persistence.
3
+ # A ForeignKey exists for every Reference from a Concept to another Concept that's a table.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
1
7
  module ActiveFacts
2
- module Metamodel
3
-
8
+ module Persistence
4
9
  class ForeignKey
5
- attr_reader :from, :to, :reference, :from_columns, :to_columns
6
- def initialize(from, to, fk_ref, from_columns, to_columns)
10
+ # What table (Concept) is the FK from?
11
+ def from; @from; end
12
+
13
+ # What table (Concept) is the FK to?
14
+ def to; @to; end
15
+
16
+ # What reference created the FK?
17
+ def reference; @reference; end
18
+
19
+ # What columns in the *from* table form the FK
20
+ def from_columns; @from_columns; end
21
+
22
+ # What columns in the *to* table form the identifier
23
+ def to_columns; @to_columns; end
24
+
25
+ def initialize(from, to, fk_ref, from_columns, to_columns) #:nodoc:
7
26
  @from, @to, @fk_ref, @from_columns, @to_columns =
8
27
  from, to, fk_ref, from_columns, to_columns
9
28
  end
10
29
  end
30
+ end
11
31
 
32
+ module Metamodel #:nodoc:
12
33
  class Concept
34
+ # When an EntityType is fully absorbed, its foreign keys are too.
35
+ # Return an Array of Reference paths for such absorbed FKs
13
36
  def all_absorbed_foreign_key_reference_path
14
37
  references_from.inject([]) do |array, ref|
15
38
  if ref.is_simple_reference
@@ -23,6 +46,7 @@ module ActiveFacts
23
46
  end
24
47
  end
25
48
 
49
+ # Return an array of all the foreign keys from this table
26
50
  def foreign_keys
27
51
  fk_ref_paths = all_absorbed_foreign_key_reference_path
28
52
 
@@ -78,7 +102,7 @@ module ActiveFacts
78
102
  end
79
103
  debug :fk, "to_columns in #{to.name}: #{to_columns.map { |column| column ? column.name : "OOPS!" }*", "}"
80
104
 
81
- ForeignKey.new(self, to, fk_ref_path[-1], from_columns, to_columns)
105
+ ActiveFacts::Persistence::ForeignKey.new(self, to, fk_ref_path[-1], from_columns, to_columns)
82
106
  end
83
107
  end
84
108
  end
@@ -1,31 +1,49 @@
1
1
  #
2
- # An Index on a Concept is used to represent a unique constraint across roles absorbed
3
- # into that concept's table.
2
+ # ActiveFacts Relational mapping and persistence.
3
+ # An Index on a Concept is used to represent a unique constraint across roles absorbed
4
+ # into that concept's table.
4
5
  #
5
- # Reference objects update each concept's list of the references *to* and *from* that concept.
6
- #
7
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
6
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
8
7
  #
9
8
 
10
9
  module ActiveFacts
11
- module Metamodel
10
+ module Persistence
12
11
  class Index
13
- attr_reader :uniqueness_constraint, :on, :over, :columns, :is_primary, :is_unique
12
+ # The UniquenessConstraint that created this index
13
+ def uniqueness_constraint; @uniqueness_constraint; end
14
+
15
+ # The table that the index is on
16
+ def on; @on; end
17
+
18
+ # If a non-mandatory reference was absorbed, only the non-nil instances are unique.
19
+ # Return the Concept that was absorbed, which might differ from this Index's table.
20
+ def over; @over; end
21
+
22
+ # Return the array of columns in this index
23
+ def columns; @columns; end
24
+
25
+ # Is this index the primary key for this table?
26
+ def is_primary; @is_primary; end
27
+
28
+ # Is this index unique?
29
+ def is_unique; @is_unique; end
14
30
 
15
31
  # An Index arises from a uniqueness constraint and applies to a table,
16
32
  # but because the UC may actually be over an object absorbed into the table,
17
33
  # we must record that object also.
18
34
  # We record the columns it's over, whether it's primary (for 'over'),
19
35
  # and whether it's unique (always, at present)
20
- def initialize(uc, on, over, columns, is_primary, is_unique = true)
36
+ def initialize(uc, on, over, columns, is_primary, is_unique = true) #:nodoc:
21
37
  @uniqueness_constraint, @on, @over, @columns, @is_primary, @is_unique =
22
38
  uc, on, over, columns, is_primary, is_unique
23
39
  end
24
40
 
41
+ # The name that was assigned (perhaps implicitly by NORMA)
25
42
  def real_name
26
43
  @uniqueness_constraint.name && @uniqueness_constraint.name != '' ? @uniqueness_constraint.name : nil
27
44
  end
28
45
 
46
+ # This name is either the name explicitly assigned (if any) or is constructed to form a unique index name.
29
47
  def name
30
48
  uc = @uniqueness_constraint
31
49
  r = real_name
@@ -35,35 +53,41 @@ module ActiveFacts
35
53
  (uc.is_preferred_identifier ? "" : "By"+column_names*"")
36
54
  end
37
55
 
38
- def abbreviated_column_names
39
- columns.map{|column| column.name.sub(/^#{over.name}/,'')}
40
- end
41
-
56
+ # An array of the names of the columns this index covers
42
57
  def column_names
43
58
  columns.map{|column| column.name}
44
59
  end
45
60
 
61
+ # An array of the names of the columns this index covers, with some lexical truncations.
62
+ def abbreviated_column_names
63
+ columns.map{|column| column.name.sub(/^#{over.name}/,'')}
64
+ end
65
+
66
+ # The name of a view that can be created to enforce uniqueness over non-null key values
46
67
  def view_name
47
68
  "#{over.name}#{on == over ? "" : "In"+on.name}"
48
69
  end
49
70
 
50
- def to_s
71
+ def to_s #:nodoc:
51
72
  name = @uniqueness_constraint.name
52
73
  colnames = @columns.map(&:name)*", "
53
74
  preferred = @uniqueness_constraint.is_preferred_identifier ? " (preferred)" : ""
54
75
  "Index #{name} on #{@on.name} over #{@over.name}(#{colnames})#{preferred}"
55
76
  end
56
77
  end
78
+ end
57
79
 
80
+ module Metamodel #:nodoc:
58
81
  class Concept
59
- attr_reader :indices
82
+ # An array of each Index for this table
83
+ def indices; @indices; end
60
84
 
61
- def clear_indices
85
+ def clear_indices #:nodoc:
62
86
  # Clear any previous indices
63
87
  @indices = nil
64
88
  end
65
89
 
66
- def populate_indices
90
+ def populate_indices #:nodoc:
67
91
  # The absorption path of a column indicates how it came to be in this table.
68
92
  # It might be a direct many:one valuetype relationship, or it might be in such
69
93
  # a relationship to an entity that was absorbed into this table (and so on).
@@ -96,14 +120,20 @@ module ActiveFacts
96
120
  !pc.max_frequency or # No maximum freq; cannot be a uniqueness constraint
97
121
  pc.max_frequency != 1 or # maximum is not 1
98
122
  pc.role_sequence.all_role_ref.size == 1 && # UniquenessConstraint is over one role
99
- (pc.role_sequence.all_role_ref[0].role.fact_type.is_a?(TypeInheritance) || # Inheritance
100
- pc.role_sequence.all_role_ref[0].role.fact_type.all_role.size == 1) # Unary
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
101
125
  # The preceeeding two restrictions exclude the internal UCs created within NORMA.
102
126
  end
103
127
  next unless pcs.size > 0
104
128
  # The columns for this ref_path support the UCs in "pcs".
105
129
  pcs.each do |pc|
106
- (columns_by_unique_constraint[pc] ||= []).concat(all_column_by_ref_path[ref_path])
130
+ ref_columns = all_column_by_ref_path[ref_path]
131
+ ordinal = role_ref.ordinal # Position in priority order
132
+ ref_columns.each_with_index do |column, index|
133
+ #puts "Adding index column #{column.name} in rank[#{ordinal},#{index}]"
134
+ # REVISIT: the "index" here might be a duplicate in some cases: change sort_by below to just sort and run the SeparateSubtypes CQL model for example.
135
+ (columns_by_unique_constraint[pc] ||= []) << [ordinal, index, column]
136
+ end
107
137
  end
108
138
  hash[role_ref] = all_column_by_ref_path[ref_path]
109
139
  end
@@ -112,17 +142,19 @@ module ActiveFacts
112
142
  end
113
143
 
114
144
  debug :index, "All Indices in #{name}:" do
115
- @indices = columns_by_unique_constraint.map do |uc, columns|
145
+ @indices = columns_by_unique_constraint.map do |uc, columns_with_ordinal|
146
+ #puts "Index on #{name} over (#{columns_with_ordinal.sort.map{|ca| [ca[0], ca[1], ca[2].name].inspect}})"
147
+ columns = columns_with_ordinal.sort_by{|ca| [ca[0,2], ca[2].name]}.map{|ca| ca[2]}
116
148
  absorption_level = columns.map(&:absorption_level).min
117
149
  over = columns[0].references[absorption_level].from
118
150
 
119
151
  # Absorption through a one-to-one forms a UC that we don't need to enforce using an index:
120
- next if over != self and
152
+ next nil if over != self and
121
153
  over.absorbed_via == columns[0].references[absorption_level-1] and
122
154
  (rrs = uc.role_sequence.all_role_ref).size == 1 and
123
- over.absorbed_via.fact_type.all_role.include?(rrs[0].role)
155
+ over.absorbed_via.fact_type.all_role.include?(rrs.only.role)
124
156
 
125
- index = Index.new(
157
+ index = ActiveFacts::Persistence::Index.new(
126
158
  uc,
127
159
  self,
128
160
  over,
@@ -131,14 +163,14 @@ module ActiveFacts
131
163
  )
132
164
  debug :index, index
133
165
  index
134
- end
166
+ end.compact
135
167
  end
136
168
  end
137
169
 
138
170
  end
139
171
 
140
172
  class Vocabulary
141
- def populate_all_indices
173
+ def populate_all_indices #:nodoc:
142
174
  debug :index, "Populating all concept indices" do
143
175
  all_feature.each do |feature|
144
176
  next unless feature.is_a? Concept