Class: ActiveFacts::RMap::Reference

Inherits:
Object
  • Object
show all
Defined in:
lib/activefacts/rmap/reference.rb,
lib/activefacts/rmap/columns.rb

Overview

This class contains the core data structure used in composing a relational schema.

A Reference is from one ObjectType to another ObjectType, and relates to the from_role and the to_role. When either ObjectType is an objectified fact type, the corresponding role is nil. When the Reference from_role is of a unary fact type, there’s no to_role or to ObjectType. The final kind of Reference is a self-reference which is added to a ValueType that becomes a table.

When the underlying fact type is a one-to-one (including an inheritance fact type), the Reference may be flipped.

Each Reference has a name; an array of names in fact, in case of adjectives, etc. Each Refererence can produce the reading of the underlying fact type.

A Reference is indexed in the player’s references_from and references_to, and flipping updates those. Finally, a Reference may be marked as absorbing the whole referenced object, and that can flip too.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(from, role) ⇒ Reference

A Reference is created from a object_type in regard to a role it plays



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/activefacts/rmap/reference.rb', line 45

def initialize(from, role)
  @fk_jump = false
  @from = from
  return unless role              # All done if it's a self-value reference for a ValueType
  @fact_type = role.fact_type
  if @fact_type.all_role.size == 1
    # @from_role is nil for a unary
    @to_role = role
    @to = role.fact_type.entity_type      # nil unless the unary is objectified
  elsif (role.fact_type.entity_type == @from)  # role is in "from", an objectified fact type
    @from_role = nil                      # Phantom role
    @to_role = role
    @to = @to_role.object_type
  else
    @from_role = role
    @to = role.fact_type.entity_type      # If set, to_role is a phantom
    unless @to
      raise "Illegal reference through >binary fact type" if @fact_type.all_role.size >2
      @to_role = (role.fact_type.all_role-[role])[0]
      @to = @to_role.object_type
    end
  end
end

Instance Attribute Details

#fact_typeObject (readonly)

Returns the value of attribute fact_type.



41
42
43
# File 'lib/activefacts/rmap/reference.rb', line 41

def fact_type
  @fact_type
end

#fk_jumpObject

True if this reference links a table to another in an FK (between absorbed references)



42
43
44
# File 'lib/activefacts/rmap/reference.rb', line 42

def fk_jump
  @fk_jump
end

#fromObject (readonly)

A “from” instance is related to one “to” instance



39
40
41
# File 'lib/activefacts/rmap/reference.rb', line 39

def from
  @from
end

#from_roleObject (readonly)

For objectified facts, one role will be nil (a phantom)



40
41
42
# File 'lib/activefacts/rmap/reference.rb', line 40

def from_role
  @from_role
end

#toObject (readonly)

A “from” instance is related to one “to” instance



39
40
41
# File 'lib/activefacts/rmap/reference.rb', line 39

def to
  @to
end

#to_roleObject (readonly)

For objectified facts, one role will be nil (a phantom)



40
41
42
# File 'lib/activefacts/rmap/reference.rb', line 40

def to_role
  @to_role
end

Instance Method Details

#columns(excluded_supertypes) ⇒ Object

:nodoc:



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/activefacts/rmap/columns.rb', line 194

def columns(excluded_supertypes)  #:nodoc:
  kind = ""
  cols = 
    if is_unary
      kind = "unary "
      objectified_unary_columns =
        ((@to && @to.fact_type) ?  @to.all_columns(excluded_supertypes) : [])

=begin
      # This code omits the unary if it's objectified and that plays a mandatory role
      first_mandatory_column = nil
      if (@to && @to.fact_type)
        trace :unary_col, "Deciding whether to skip unary column for #{inspect}" do
          first_mandatory_column =
            objectified_unary_columns.detect do |col|     # Detect a mandatory column for the unary
              trace :unary_col, "checking column #{col.name}" do
                !col.references.detect do |ref|
                  trace :unary_col, "#{ref} is mandatory=#{ref.is_mandatory.inspect}"
                  !ref.is_mandatory
                end
              end
            end
          if is_from_objectified_fact && first_mandatory_column
            trace :unary_col, "Skipping unary column for #{inspect} because #{first_mandatory_column.name} is mandatory"
          end
        end
      end

      (is_from_objectified_fact && first_mandatory_column ? [] : [Column.new()]) +          # The unary itself, unless its objectified
=end

      [Column.new()] +      # The unary itself
        objectified_unary_columns
    elsif is_self_value
      kind = "self-role "
      [Column.new()]
    elsif is_simple_reference
      @to.reference_columns(excluded_supertypes)
    else
      kind = "absorbing "
      @to.all_columns(excluded_supertypes)
    end

  cols.each do |c|
    c.prepend self
  end

  trace :columns, "Columns from #{kind}#{self}" do
    cols.each {|c|
      trace :columns, "#{c}"
    }
  end
end

#detabulateObject

:nodoc:



197
198
199
200
201
202
203
# File 'lib/activefacts/rmap/reference.rb', line 197

def detabulate        #:nodoc:
  # Remove from @to and @from's reference lists if present
  return unless @from.references_from.delete(self)
  @to.references_to.delete self if @to    # Guard against self-values
  trace :references, "Dropping #{to_s}"
  self
end

#flipObject

For a one-to-one (or a subtyping fact type), reverse the direction.



162
163
164
165
166
167
168
# File 'lib/activefacts/rmap/reference.rb', line 162

def flip                          #:nodoc:
  raise "Illegal flip of #{self}" unless @to and is_one_to_one

  detabulate
  mirror
  tabulate
end

#from_namesObject

Return the array of names for the (perhaps implicit) from_role of this Reference



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/activefacts/rmap/reference.rb', line 137

def from_names
  case
  when @from && !@from_role           # @from is an objectified fact type so @from_role is a phantom
    @from.name.camelwords
  when is_unary
    if @from && @from.fact_type
      @from.name.camelwords
    else
      @from_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
    end
  when !@from_role                  # Self-value role of an independent ValueType
    @from.name.camelwords + ["Value"]
  when @from_role.role_name         # Named role
    @from_role.role_name.camelwords
  else                            # Use the name from the preferred reading
    role_ref = @from_role.preferred_reference
    [role_ref.leading_adjective, @from_role.object_type.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''}
  end
end

#inspectObject

:nodoc:



235
236
237
# File 'lib/activefacts/rmap/reference.rb', line 235

def inspect #:nodoc:
  to_s
end

#is_absorbingObject

Is the to object_type fully absorbed through this reference?



104
105
106
# File 'lib/activefacts/rmap/reference.rb', line 104

def is_absorbing
  @to && @to.absorbed_via == self
end

#is_from_objectified_factObject

If this Reference is from an objectified FactType, there is no from_role



94
95
96
# File 'lib/activefacts/rmap/reference.rb', line 94

def is_from_objectified_fact
  @to && !@from_role && @to_role
end

#is_mandatoryObject

Is this Reference covered by a mandatory constraint (implicitly or explicitly)



76
77
78
79
80
# File 'lib/activefacts/rmap/reference.rb', line 76

def is_mandatory
  !is_unary &&
    (!@from_role ||        # All phantom roles of fact types are mandatory
      @from_role.is_mandatory)
end

#is_one_to_oneObject



157
158
159
# File 'lib/activefacts/rmap/reference.rb', line 157

def is_one_to_one
  [:one_one, :subtype, :supertype].include?(role_type)
end

#is_self_valueObject

Is this reference an injected role as a result a ValueType being a table?



99
100
101
# File 'lib/activefacts/rmap/reference.rb', line 99

def is_self_value
  !@to && !@to_role
end

#is_simple_referenceObject

Is this a simple reference?



109
110
111
112
113
# File 'lib/activefacts/rmap/reference.rb', line 109

def is_simple_reference
  # It's a simple reference to a thing if that thing is a table,
  # or is fully absorbed into another table but not via this reference.
  @to && (@to.is_table or @to.absorbed_via && !is_absorbing)
end

#is_to_objectified_factObject

If this Reference is to an objectified FactType, there is no to_role



88
89
90
91
# File 'lib/activefacts/rmap/reference.rb', line 88

def is_to_objectified_fact
  # This case is the only one that cannot be used in the preferred identifier of @from
  @to && !@to_role && @from_role
end

#is_unaryObject

Is this Reference from a unary Role?



83
84
85
# File 'lib/activefacts/rmap/reference.rb', line 83

def is_unary
  @to_role && @to_role.fact_type.all_role.size == 1
end

#mirrorObject

Create a (non-tabulated) flipped version of this Reference. Careful not to tabulate it!



171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/activefacts/rmap/reference.rb', line 171

def mirror
  if @to.absorbed_via == self
    @to.absorbed_via = nil
    @from.absorbed_via = self
  end

  # Flip the reference
  @to, @from = @from, @to
  @to_role, @from_role = @from_role, @to_role
  trace :references, "Mirror #{self.inspect} absorbs #{@to.name}" if @to.absorbed_via == self
  self
end

#readingObject

The reading for the fact type underlying this Reference



211
212
213
# File 'lib/activefacts/rmap/reference.rb', line 211

def reading
  is_self_value ? "#{from.name} has value" : @fact_type.reading_preferably_starting_with_role(@from_role).expand
end

#reversedObject



184
185
186
# File 'lib/activefacts/rmap/reference.rb', line 184

def reversed
  clone.mirror
end

#role_typeObject

What type of Role did this Reference arise from?



70
71
72
73
# File 'lib/activefacts/rmap/reference.rb', line 70

def role_type
  role = @from_role||@to_role
  role && role.role_type
end

#tabulateObject

:nodoc:



188
189
190
191
192
193
194
195
# File 'lib/activefacts/rmap/reference.rb', line 188

def tabulate        #:nodoc:
  # Add to @to and @from's reference lists
  @from.references_from << self
  @to.references_to << self if @to        # Guard against self-values

  trace :references, "Adding #{to_s}"
  self
end

#to_names(is_prefix = true) ⇒ Object

Return the array of names for the (perhaps implicit) to_role of this Reference



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/activefacts/rmap/reference.rb', line 116

def to_names(is_prefix = true)
  case
  when is_unary
    if @to && @to.fact_type && is_prefix
      @to.name.camelwords
    else
      @to_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
    end
  when @to && !@to_role           # @to is an objectified fact type so @to_role is a phantom
    @to.name.camelwords
  when !@to_role                  # Self-value role of an independent ValueType
    @from.name.camelwords + ["Value"]
  when @to_role.role_name         # Named role
    @to_role.role_name.camelwords
  else                            # Use the name from the preferred reading
    role_ref = @to_role.preferred_reference
    [role_ref.leading_adjective, @to_role.object_type.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''}
  end
end

#to_sObject

:nodoc:



205
206
207
208
# File 'lib/activefacts/rmap/reference.rb', line 205

def to_s              #:nodoc:
  ref_type = fk_jump ? "jumping to" : (is_absorbing ? "absorbing" : "to")
  "reference from #{@from.name}#{@to ? " #{ref_type} #{@to.name}" : ""}" + (@fact_type ? " in '#{@fact_type.default_reading}'" : "")
end

#verbalised_path(reverse = false) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/activefacts/rmap/reference.rb', line 215

def verbalised_path reverse = false
  return "#{from.name} Value" if is_self_value
  objectified = fact_type.entity_type 
  f =     # Switch to the Link Fact Type if we're traversing an objectification
    (to_role && to_role.link_fact_type) ||
    (from_role && from_role.link_fact_type) ||
    fact_type

  start_role =
    if objectified
      target = reverse ? to : from
      [to_role, from_role, f.all_role_in_order[0]].compact.detect{|role| role.object_type == target}
    else
      reverse ? to_role : from_role
    end
  reading = f.reading_preferably_starting_with_role(start_role)
  (is_mandatory || is_unary ? '' : 'maybe ') +
    reading.expand
end