Class: ActiveFacts::Metamodel::ObjectType

Inherits:
Object
  • Object
show all
Defined in:
lib/activefacts/persistence/columns.rb,
lib/activefacts/mapping/rails.rb,
lib/activefacts/persistence/index.rb,
lib/activefacts/vocabulary/metamodel.rb,
lib/activefacts/persistence/reference.rb,
lib/activefacts/vocabulary/extensions.rb,
lib/activefacts/generate/helpers/rails.rb,
lib/activefacts/persistence/foreignkey.rb,
lib/activefacts/generate/transform/surrogate.rb

Overview

The ObjectType class is defined in the metamodel; full documentation is not generated. This section shows the features relevant to relational Persistence.

Direct Known Subclasses

DomainObjectType

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#injected_surrogate_roleObject (readonly)

Placeholder for the surrogate transform



413
414
415
# File 'lib/activefacts/vocabulary/extensions.rb', line 413

def injected_surrogate_role
  @injected_surrogate_role
end

#is_table=(value) ⇒ Object (writeonly)

The two ObjectType subclasses provide the attr_reader method



245
246
247
# File 'lib/activefacts/persistence/reference.rb', line 245

def is_table=(value)
  @is_table = value
end

#tentativeObject

Say whether the independence of this object is still under consideration This is used in detecting dependency cycles, such as occurs in the Metamodel



244
245
246
# File 'lib/activefacts/persistence/reference.rb', line 244

def tentative
  @tentative
end

Instance Method Details

#add_surrogate(type_name = 'Auto Counter', suffix = 'ID') ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/activefacts/generate/transform/surrogate.rb', line 14

def add_surrogate type_name = 'Auto Counter', suffix = 'ID'
  # Find or assert the surrogate value type
  auto_counter = vocabulary.valid_value_type_name(type_name) ||
    constellation.ValueType(:vocabulary => vocabulary, :name => type_name, :concept => :new)

  # Create a subtype to identify this entity type:
  vt_name = self.name + ' '+suffix
  my_id = @vocabulary.valid_value_type_name(vt_name) ||
    constellation.ValueType(:vocabulary => vocabulary, :name => vt_name, :concept => :new, :supertype => auto_counter)

  # Create a fact type
  identifying_fact_type = constellation.FactType(:concept => :new)
  my_role = constellation.Role(:concept => :new, :fact_type => identifying_fact_type, :ordinal => 0, :object_type => self)
  @injected_surrogate_role = my_role
  id_role = constellation.Role(:concept => :new, :fact_type => identifying_fact_type, :ordinal => 1, :object_type => my_id)

  # Create a reading (which needs a RoleSequence)
  reading = constellation.Reading(
    :fact_type => identifying_fact_type,
    :ordinal => 0,
    :role_sequence => [:new],
    :text => "{0} has {1}"
  )
  constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 0, :role => my_role)
  constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 1, :role => id_role)

  # Create two uniqueness constraints for the one-to-one. Each needs a RoleSequence (two RoleRefs)
  one_id = constellation.PresenceConstraint(
      :concept => :new,
      :vocabulary => vocabulary,
      :name => self.name+'HasOne'+suffix,
      :role_sequence => [:new],
      :is_mandatory => true,
      :min_frequency => 1,
      :max_frequency => 1,
      :is_preferred_identifier => false
    )
  @constellation.RoleRef(:role_sequence => one_id.role_sequence, :ordinal => 0, :role => my_role)

  one_me = constellation.PresenceConstraint(
      :concept => :new,
      :vocabulary => vocabulary,
      :name => self.name+suffix+'IsOfOne'+self.name,
      :role_sequence => [:new],
      :is_mandatory => false,
      :min_frequency => 0,
      :max_frequency => 1,
      :is_preferred_identifier => true
    )
  @constellation.RoleRef(:role_sequence => one_me.role_sequence, :ordinal => 0, :role => id_role)
end

#all_absorbed_foreign_key_reference_pathObject

When an EntityType is fully absorbed, its foreign keys are too. Return an Array of Reference paths for such absorbed FKs



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/activefacts/persistence/foreignkey.rb', line 96

def all_absorbed_foreign_key_reference_path
  references_from.inject([]) do |array, ref|
    if ref.is_simple_reference
      if TypeInheritance === ref.fact_type
        # Ignore references to secondary supertypes, when absorption is through primary.
        next array if absorbed_via && TypeInheritance === absorbed_via.fact_type
        # Ignore the case where a subtype is absorbed elsewhere:
        # REVISIT: Disabled, as this should never happen.
        # next array if ref.to.absorbed_via != ref.fact_type
      end
	    ref.fk_jump = true
      array << [ref]
    elsif ref.is_absorbing or (ref.to && !ref.to.is_table)
	    trace :fk, "getting fks absorbed into #{name} via #{ref}" do
 ref.to.all_absorbed_foreign_key_reference_path.each do |aref|
		array << aref.insert(0, ref)
 end
	    end
    end
    array
  end
end

#clear_indicesObject

:nodoc:



110
111
112
113
# File 'lib/activefacts/persistence/index.rb', line 110

def clear_indices     #:nodoc:
  # Clear any previous indices
  @indices = nil
end

#clear_referencesObject

:nodoc:



287
288
289
290
291
# File 'lib/activefacts/persistence/reference.rb', line 287

def clear_references              #:nodoc:
  # Clear any previous references:
  @references_to = nil
  @references_from = nil
end

#columnsObject

The array of columns for this ObjectType’s table



251
252
253
# File 'lib/activefacts/persistence/columns.rb', line 251

def columns
  @columns || populate_columns
end

#definitely_not_tableObject

:nodoc:



257
258
259
260
# File 'lib/activefacts/persistence/reference.rb', line 257

def definitely_not_table          #:nodoc:
  @is_table = false
  @tentative = false
end

#definitely_tableObject

:nodoc:



252
253
254
255
# File 'lib/activefacts/persistence/reference.rb', line 252

def definitely_table              #:nodoc:
  @is_table = true
  @tentative = false
end

#foreign_keysObject

Return an array of all the foreign keys from this table



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/activefacts/persistence/foreignkey.rb', line 124

def foreign_keys

  # Get the ForeignKey object for each absorbed reference path
	@foreign_keys ||=
	  begin
	    fk_ref_paths = all_absorbed_foreign_key_reference_path
	    fk_ref_paths.map do |fk_ref_path|
 trace :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do

		from_columns = (columns||all_columns({})).select{|column|
		  column.references[0...fk_ref_path.size] == fk_ref_path
		}
		trace :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"

		# Figure out absorption on the target end:
		to = fk_ref_path.last.to
		if to.absorbed_via
		  trace :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do
while (r = to.absorbed_via)
  m = r.reversed
  trace :fk, "#{m.reading}"
  fk_ref_path << m
  to = m.from == to ? m.to : m.from
end
trace :fk, "Absorption ends at #{to.name}"
		  end
		end

		# REVISIT: This test may no longer be necessary
		raise "REVISIT: #{fk_ref_path.inspect} is bad" unless to and to.columns

		# REVISIT: This fails for absorbed subtypes having their own identification.
		# Check the CompanyDirectorEmployee model for example, EmployeeManagerNr -> Person (should reference EmployeeNr)
		# Need to use the absorbed identifier_columns of the subtype,
		# not the columns of the supertype that absorbs it.
		# But in general, that isn't going to work because in most DBMS
		# there's no suitable uniquen index on the subtype's identifier_columns

		to_columns = fk_ref_path[-1].to.identifier_columns

		# Put the column pairs in the correct order. They MUST be in the order they appear in the primary key
		froms, tos = from_columns.zip(to_columns).sort_by { |pair|
		  to_columns.index(pair[1])
		}.transpose

		fk = ActiveFacts::Persistence::ForeignKey.new(self, to, fk_ref_path, froms, tos)
		to.foreign_keys_to << fk
		fk
 end
	    end.
	    sort_by do |fk|
 # Put the foreign keys in a defined order:
#	      debugger if !fk.to_columns || fk.to_columns.include?(nil) || !fk.from_columns || fk.from_columns.include?(nil)
 [ fk.to.name,
		fk.to_columns.map{|col| col.name(nil).sort},
		fk.from_columns.map{|col| col.name(nil).sort}
 ]
	    end
	  end

end

#foreign_keys_toObject



119
120
121
# File 'lib/activefacts/persistence/foreignkey.rb', line 119

def foreign_keys_to
	@foreign_keys_to ||= []
end

#has_referencesObject

True if this ObjectType has any References (to or from)



283
284
285
# File 'lib/activefacts/persistence/reference.rb', line 283

def has_references                #:nodoc:
  @references_from || @references_to
end

#indicesObject

An array of each Index for this table



106
107
108
# File 'lib/activefacts/persistence/index.rb', line 106

def indices
	@indices || populate_indices
end

#is_separateObject



415
416
417
# File 'lib/activefacts/vocabulary/extensions.rb', line 415

def is_separate
	is_independent or concept.all_concept_annotation.detect{|ca| ca.mapping_annotation == 'separate'}
end

#populate_columnsObject

:nodoc:



255
256
257
258
# File 'lib/activefacts/persistence/columns.rb', line 255

def populate_columns  #:nodoc:
  @columns =
    all_columns({})
end

#populate_indicesObject

:nodoc:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/activefacts/persistence/index.rb', line 115

def populate_indices     #:nodoc:
  # The absorption path of a column indicates how it came to be in this table.
  # It might be a direct many:one valuetype relationship, or it might be in such
  # a relationship to an entity that was absorbed into this table (and so on).
  # The reference path is the set of absorption references and one past it.
  # Stopping here means we don't dig into the definitions of FK column counterparts.
  # Note that many columns of an object may have the same ref_path.
  #
  # REVISIT:
  # Note also that this produces columns ordered for each refpath the same as the
  # order of the columns, not the same as the columns in the PK for which they might be an FK.
  all_column_by_ref_path =
    trace :index2, "Indexing columns by ref_path" do
      columns.inject({}) do |hash, column|
        trace :index2, "References in column #{name}.#{column.name}" do
          ref_path = column.absorption_references
          raise "No absorption_references for #{column.name} from #{column.references.map(&:to_s)*" and "}" if !ref_path || ref_path.empty?
          (hash[ref_path] ||= []) << column
          trace :index2, "#{column.name} involves #{ref_path.map(&:to_s)*" and "}"
        end
        hash
      end
    end

  columns_by_unique_constraint = {}
  all_column_by_role_ref =
    all_column_by_ref_path.
      keys.                       # Go through all refpaths and find uniqueness constraints
      inject({}) do |hash, ref_path|
        ref_path.each do |ref|
          next unless ref.to_role
          # trace :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name('.')}*", "}"
          ref.to_role.all_role_ref.each do |role_ref|
            all_pcs = role_ref.role_sequence.all_presence_constraint
		  # 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
            pcs = all_pcs.
              reject do |pc|
                !pc.max_frequency or      # No maximum freq; cannot be a uniqueness constraint
                pc.max_frequency != 1 or  # maximum is not 1
                                          # Constraint is not over a unary fact type role (NORMA does this)
                pc.role_sequence.all_role_ref.size == 1 && ref_path[-1].to_role.fact_type.all_role.size == 1
              end
            next unless pcs.size > 0
            # The columns for this ref_path support the UCs in "pcs".
            pcs.each do |pc|
              ref_columns = all_column_by_ref_path[ref_path]
              ordinal = role_ref.ordinal  # Position in priority order
              ref_columns.each_with_index do |column, index|
                #puts "Adding index column #{column.name} in rank[#{ordinal},#{index}]"
                # 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.
                (columns_by_unique_constraint[pc] ||= []) << [ordinal, index, column]
              end
            end
            hash[role_ref] = all_column_by_ref_path[ref_path]
          end
        end
        hash
      end

  trace :index, "All Indices in #{name}:" do
    @indices = columns_by_unique_constraint.map do |uc, columns_with_ordinal|
      trace :index, "Index due to uc #{uc.concept.guid} on #{name} over (#{columns_with_ordinal.sort_by{|onc|onc[0]}.map{|ca| ca[2].name}.inspect})"
      columns = columns_with_ordinal.sort_by{|ca| [ca[0,2], ca[2].name]}.map{|ca| ca[2]}
      absorption_level = columns.map(&:absorption_level).min
      over = columns[0].references[absorption_level].from

      # Absorption through a one-to-one forms a UC that we don't need to enforce using an index:
	    if over != self and
        over.absorbed_via == columns[0].references[absorption_level-1] and
        (rr = uc.role_sequence.all_role_ref.single) and
        over.absorbed_via.fact_type.all_role.include?(rr.role)
 next nil
	    end

      index = ActiveFacts::Persistence::Index.new(
        uc,
        self,
        over,
        columns,
        uc.is_preferred_identifier
      )
      trace :index, index
      index
    end.
    compact.
    sort_by do |index|
      # Put the indices in a defined order:
      index.columns.map(&:name)+['', index.over.name]
    end
  end
	si = self_index
	@indices.unshift(si) if si
	@indices
end

#populate_reference(role) ⇒ Object

:nodoc:



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/activefacts/persistence/reference.rb', line 306

def populate_reference role       #:nodoc:
  role_type = role.role_type
  trace :references, "#{name} has #{role_type} role in '#{role.fact_type.describe}'"
  case role_type
  when :many_one
    ActiveFacts::Persistence::Reference.new(self, role).tabulate      # A simple reference

  when :one_many
    if role.fact_type.entity_type == self   # A Role of this objectified FactType
      ActiveFacts::Persistence::Reference.new(self, role).tabulate    # A simple reference; check that
    else
      # Can't absorb many of these into one of those
      #trace :references, "Ignoring #{role_type} reference from #{name} to #{Reference.new(self, role).to.name}"
    end

  when :unary
    ActiveFacts::Persistence::Reference.new(self, role).tabulate      # A simple reference

  when :supertype   # A subtype absorbs a reference to its supertype when separate, or all when partitioned
    # REVISIT: Or when partitioned
    raise "Internal error, expected TypeInheritance" unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
	  counterpart_role = (role.fact_type.all_role.to_a-[role])[0]
    if role.fact_type.assimilation or counterpart_role.object_type.is_separate
      trace :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}"
    else
      r = ActiveFacts::Persistence::Reference.new(self, role)
      r.to.absorbed_via = r
      trace :references, "supertype #{name} absorbs subtype #{r.to.name}"
      r.tabulate
    end

  when :subtype    # This object is a supertype, which can absorb the subtype unless that's independent
    if role.fact_type.assimilation or is_separate
      ActiveFacts::Persistence::Reference.new(self, role).tabulate
      # If partitioned, the supertype is absorbed into *each* subtype; a reference to the supertype needs to know which
    else
      # trace :references, "subtype #{name} is absorbed into #{role.fact_type.supertype.name}"
    end

  when :one_one
    r = ActiveFacts::Persistence::Reference.new(self, role)

    # Decide which way the one-to-one is likely to go; it will be flipped later if necessary.
    # Force the decision if just one is independent:
    # REVISIT: Decide whether supertype assimilation can affect this
    r.tabulate and return if is_separate and !r.to.is_separate
    return if !is_separate and r.to.is_separate

    if is_a?(ValueType)
      # Never absorb an entity type into a value type
      return if r.to.is_a?(EntityType)  # Don't tabulate it
    else
      if r.to.is_a?(ValueType)
        r.tabulate  # Always absorb a value type into an entity type
        return 
      end

      # Force the decision if one EntityType identifies another:
      if preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == r.to_role}
        trace :references, "EntityType #{name} is identified by EntityType #{r.to.name}, so gets absorbed elsewhere"
        return
      end
      if r.to.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == role}
        trace :references, "EntityType #{name} identifies EntityType #{r.to.name}, so absorbs it"
        r.to.absorbed_via = r
        # We can't be absorbed into our supertype!
        # REVISIT: We might need to flip all one-to-ones as well
        r.to.references_to.clone.map{|q|q.flip if q.to_role.role_type == :subtype }
        r.tabulate
        return
      end
    end

    # Either both EntityTypes, or both ValueTypes.
    # Make an arbitrary (but stable) decision which way to go. We might flip it later,
    # but not frivolously; the Ruby API column name generation duplicates this logic.
    unless r.from.name.downcase < r.to.name.downcase or
      (r.from == r.to && references_to.detect{|ref| ref.to_role == role}) # one-to-one self reference, done already
      r.tabulate
    end
  else
	  # REVISIT: Should we implicitly objectify this fact type here and add a spanning UC?
    raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint"
  end
end

#populate_referencesObject

:nodoc:



293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/activefacts/persistence/reference.rb', line 293

def populate_references           #:nodoc:
  all_role.each do |role|
    # It's possible that this role is in an implicit or derived fact type. Skip it if so.
    next if role.fact_type.is_a?(LinkFactType) or
	    # REVISIT: dafuq? Is this looking for a constraint over a derivation? This looks wrong.
      role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].play or
	    # This is not yet actually set, and wouldn't handle constraint derivations anyhow:
	    role.variable_as_projection
    
    populate_reference role
  end
end

#probably_not_tableObject

:nodoc:



267
268
269
270
# File 'lib/activefacts/persistence/reference.rb', line 267

def probably_not_table            #:nodoc:
  @is_table = false
  @tentative = true
end

#probably_tableObject

:nodoc:



262
263
264
265
# File 'lib/activefacts/persistence/reference.rb', line 262

def probably_table                #:nodoc:
  @is_table = true
  @tentative = true
end

#rails_class_nameObject



127
128
129
# File 'lib/activefacts/mapping/rails.rb', line 127

def rails_class_name
	ActiveSupport::Inflector.camelize(name.gsub(/\s+/, '_'))
end

#rails_nameObject



119
120
121
# File 'lib/activefacts/mapping/rails.rb', line 119

def rails_name
	Persistence::rails_plural_name(name)
end

#rails_singular_nameObject



123
124
125
# File 'lib/activefacts/mapping/rails.rb', line 123

def rails_singular_name
	Persistence::rails_singular_name(name)
end

#references_fromObject

References from this ObjectType



273
274
275
# File 'lib/activefacts/persistence/reference.rb', line 273

def references_from
  @references_from ||= []
end

#references_toObject

References to this ObjectType



278
279
280
# File 'lib/activefacts/persistence/reference.rb', line 278

def references_to
  @references_to ||= []
end

#show_tabularObject

:nodoc:



247
248
249
250
# File 'lib/activefacts/persistence/reference.rb', line 247

def show_tabular                  #:nodoc:
  (tentative ? "tentatively " : "") +
  (is_table ? "" : "not ")+"a table"
end

#wipe_columnsObject



260
261
262
# File 'lib/activefacts/persistence/columns.rb', line 260

def wipe_columns
	@columns = nil
end