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



250
251
252
# File 'lib/activefacts/vocabulary/extensions.rb', line 250

def injected_surrogate_role
  @injected_surrogate_role
end

#is_table=(value) ⇒ Object (writeonly)

The two ObjectType subclasses provide the attr_reader method



224
225
226
# File 'lib/activefacts/persistence/reference.rb', line 224

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



223
224
225
# File 'lib/activefacts/persistence/reference.rb', line 223

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



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

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)
	    debug :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:



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

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



227
228
229
# File 'lib/activefacts/persistence/columns.rb', line 227

def columns
  @columns || populate_columns
end

#definitely_not_tableObject

:nodoc:



236
237
238
239
# File 'lib/activefacts/persistence/reference.rb', line 236

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

#definitely_tableObject

:nodoc:



231
232
233
234
# File 'lib/activefacts/persistence/reference.rb', line 231

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

#foreign_keysObject

Return an array of all the foreign keys from this table



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
# File 'lib/activefacts/persistence/foreignkey.rb', line 125

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|
 debug :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
		}
		debug :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
		  debug :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do
while (r = to.absorbed_via)
  m = r.reversed
  debug :fk, "#{m.reading}"
  fk_ref_path << m
  to = m.from == to ? m.to : m.from
end
debug :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



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

def foreign_keys_to
	@foreign_keys_to ||= []
end

#has_referencesObject

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



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

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

#populate_columnsObject

:nodoc:



231
232
233
234
# File 'lib/activefacts/persistence/columns.rb', line 231

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 =
    debug :index2, "Indexing columns by ref_path" do
      columns.inject({}) do |hash, column|
        debug :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
          debug :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
          # debug :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

  debug :index, "All Indices in #{name}:" do
    @indices = columns_by_unique_constraint.map do |uc, columns_with_ordinal|
      debug :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
      )
      debug :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:



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
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
# File 'lib/activefacts/persistence/reference.rb', line 285

def populate_reference role       #:nodoc:
  role_type = role.role_type
  debug :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
      #debug :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 hell unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
    if role.fact_type.assimilation  # assimilation == 'separate' or assimilation == 'partitioned'
      debug :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
      debug :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
      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
      # debug :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_independent and !r.to.is_independent
    return if !is_independent and r.to.is_independent

    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}
        debug :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}
        debug :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
    raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint"
  end
end

#populate_referencesObject

:nodoc:



272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/activefacts/persistence/reference.rb', line 272

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:



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

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

#probably_tableObject

:nodoc:



241
242
243
244
# File 'lib/activefacts/persistence/reference.rb', line 241

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

#rails_class_nameObject



117
118
119
# File 'lib/activefacts/mapping/rails.rb', line 117

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

#rails_nameObject



109
110
111
# File 'lib/activefacts/mapping/rails.rb', line 109

def rails_name
	Persistence::rails_plural_name(name)
end

#rails_singular_nameObject



113
114
115
# File 'lib/activefacts/mapping/rails.rb', line 113

def rails_singular_name
	Persistence::rails_singular_name(name)
end

#references_fromObject

References from this ObjectType



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

def references_from
  @references_from ||= []
end

#references_toObject

References to this ObjectType



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

def references_to
  @references_to ||= []
end

#show_tabularObject

:nodoc:



226
227
228
229
# File 'lib/activefacts/persistence/reference.rb', line 226

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