Module: Jinx::Dependency

Included in:
Metadata
Defined in:
lib/jinx/metadata/dependency.rb

Overview

Metadata mix-in to capture Resource dependency.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#owner_attributes<Symbol> (readonly)

Returns this class’s owner attributes.

Returns:

  • (<Symbol>)

    this class’s owner attributes



7
8
9
# File 'lib/jinx/metadata/dependency.rb', line 7

def owner_attributes
  @owner_attributes
end

Instance Method Details

#add_dependent_attribute(attribute, *flags) ⇒ Object

Adds the given attribute as a dependent.

Parameters:

  • attribute (Symbol)

    the dependent to add

  • property (Property)

    the dependent to add

  • flags (<Symbol>)

    the attribute qualifier flags

See Also:



42
43
44
# File 'lib/jinx/metadata/dependency.rb', line 42

def add_dependent_attribute(attribute, *flags)
  add_dependent_property(property(attribute), *flags)
end

#add_dependent_property(property, *flags) ⇒ Object

Adds the given property as a dependent.

If the property inverse is not a collection, then the property writer is modified to delegate to the dependent owner writer. This enforces referential integrity by ensuring that the following post-condition holds:

  • owner.attribute.inverse == owner

where:

  • owner is an instance this property’s declaring class

  • inverse is the owner inverse attribute defined in the dependent class

Parameters:

  • property (Property)

    the dependent to add

  • flags (<Symbol>)

    the attribute qualifier flags



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/jinx/metadata/dependency.rb', line 21

def add_dependent_property(property, *flags)
  logger.debug { "Marking #{qp}.#{property} as a dependent attribute of type #{property.type.qp}..." }
  flags << :dependent unless flags.include?(:dependent)
  property.qualify(*flags)
  inv = property.inverse
  inv_type = property.type
  # example: Parent.add_dependent_property(child_prop) with inverse :parent calls the following:
  #   Child.add_owner(Parent, :children, :parent)
  inv_type.add_owner(self, property.attribute, inv)
  if inv then
    logger.debug "Marked #{qp}.#{property} as a dependent attribute with inverse #{inv_type.qp}.#{inv}."
  else  
    logger.debug "Marked #{qp}.#{property} as a uni-directional dependent attribute."
  end
end

#add_owner(klass, inverse, attribute = nil) ⇒ Object (protected)

Adds the given owner class to this dependent class. This method must be called before any dependent attribute is accessed. If the attribute is given, then the attribute inverse is set. Otherwise, if there is not already an owner attribute, then a new owner attribute is created. The name of the new attribute is the lower-case demodulized owner class name.

Parameters:

  • the (Class)

    owner class

  • inverse (Symbol)

    the owner -> dependent attribute

  • attribute (Symbol, nil) (defaults to: nil)

    the dependent -> owner attribute, if known

Raises:



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
# File 'lib/jinx/metadata/dependency.rb', line 146

def add_owner(klass, inverse, attribute=nil)
  if inverse.nil? then
    raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}")
  end
  logger.debug { "Adding #{qp} owner #{klass.qp}#{' attribute ' + attribute.to_s if attribute} with inverse #{inverse}..." }
  if @owner_prop_hash then
    raise MetadataError.new("Can't add #{qp} owner #{klass.qp} after dependencies have been accessed")
  end
  
  # Detect the owner attribute, if necessary.
  attribute ||= detect_owner_attribute(klass, inverse)
  hash = local_owner_property_hash
  # Guard against a conflicting owner reference attribute.
  if hash[klass] then
    raise MetadataError.new("Cannot set #{qp} owner attribute to #{attribute or 'nil'} since it is already set to #{hash[klass]}")
  end
  # Add the owner class => attribute entry.
  # The attribute is nil if the dependency is unidirectional, i.e. there is an owner class which
  # references this class via a dependency attribute but there is no inverse owner attribute.
  prop = property(attribute) if attribute
  hash[klass] = prop
  # If the dependency is unidirectional, then our job is done.
  if attribute.nil? then
    logger.debug { "#{qp} owner #{klass.qp} has unidirectional dependent attribute #{inverse}." }
    return
  end

  # Bi-directional: add the owner property.
  local_owner_properties << prop
  # Set the inverse if necessary.
  unless prop.inverse then
    set_attribute_inverse(attribute, inverse)
  end
  # Set the owner flag if necessary.
  prop.qualify(:owner) unless prop.owner?

  # Redefine the writer method to issue a warning if the owner is changed.
  rdr, wtr = prop.accessors
  redefine_method(wtr) do |old_wtr|
    lambda do |ref|
      prev = send(rdr)
      send(old_wtr, ref)
      if prev and prev != ref then
        if ref.nil? then
          logger.warn("Unset the #{self} owner #{attribute} #{prev}.")
        elsif ref.key != prev.key then
          logger.warn("Reset the #{self} owner #{attribute} from #{prev} to #{ref}.")
        end
      end
      ref
    end
  end
  logger.debug { "Injected owner change warning into #{qp}.#{attribute} writer method #{wtr}." }
  logger.debug { "#{qp} owner #{klass.qp} attribute is #{attribute} with inverse #{inverse}." }
end

#add_owner_attribute(attribute) ⇒ Object (protected)

Adds the given attribute as an owner. This method is called when a new attribute is added that references an existing owner.

Parameters:

  • attribute (Symbol)

    the owner attribute



206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/jinx/metadata/dependency.rb', line 206

def add_owner_attribute(attribute)
  prop = property(attribute)
  otype = prop.type
  hash = local_owner_property_hash
  if hash.include?(otype) then
    oa = hash[otype]
    unless oa.nil? then
      raise MetadataError.new("Cannot set #{qp} owner attribute to #{attribute} since it is already set to #{oa}")
    end
    hash[otype] = prop
  else
    add_owner(otype, prop.inverse, attribute)
  end
end

#bidirectional_java_dependent?Boolean

Returns whether this Resource class is dependent and reference its owners.

Returns:

  • (Boolean)

    whether this Resource class is dependent and reference its owners



78
79
80
# File 'lib/jinx/metadata/dependency.rb', line 78

def bidirectional_java_dependent?
  dependent? and owner_properties.any? { |prop| prop.java_property? }
end

#create_owner_properties_enumerator<Property> (private)

Returns the owner properties defined in the class hierarchy.

Returns:

  • (<Property>)

    the owner properties defined in the class hierarchy



257
258
259
260
# File 'lib/jinx/metadata/dependency.rb', line 257

def create_owner_properties_enumerator
  local = local_owner_properties
  Class === self && superclass < Resource ? local.union(superclass.owner_properties) : local
end

#create_owner_property_hash{Class => Property} (private)

Returns a new owner type => attribute hash.

Returns:



246
247
248
249
# File 'lib/jinx/metadata/dependency.rb', line 246

def create_owner_property_hash
  local = local_owner_property_hash
  Class === self && superclass < Resource ? local.union(superclass.owner_property_hash) : local
end

#dependency_path_to(klass) ⇒ <Property>?

Returns the path of this class to the given class through dependent properties, or nil if there is no such path exists.

Returns:

  • (<Property>, nil)

    the path of this class to the given class through dependent properties, or nil if there is no such path exists



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/jinx/metadata/dependency.rb', line 89

def dependency_path_to(klass)
  return if klass.owner_types.empty? 
  op = klass.owner_properties.detect { |prop| self <= prop.type }
  dp = op.inverse if op
  dp ||= if klass.owner_properties.size < klass.owner_types.size then
    dependent_properties.detect { |prop| klass <= prop.type }
  end
  return [dp] if dp
  dependent_properties.detect_value do |dp|
    next if self <= dp.type
    path = dp.type.dependency_path_to(klass)
    path.unshift(dp) if path
  end
end

#dependent?Boolean

Returns whether this class depends on an owner.

Returns:

  • (Boolean)

    whether this class depends on an owner



47
48
49
# File 'lib/jinx/metadata/dependency.rb', line 47

def dependent?
  not owners.empty?
end

#dependent_attribute(klass) ⇒ Symbol?

Returns the attribute which references the dependent type, or nil if none.

Parameters:

  • klass (Class)

    the dependent type

Returns:

  • (Symbol, nil)

    the attribute which references the dependent type, or nil if none



63
64
65
# File 'lib/jinx/metadata/dependency.rb', line 63

def dependent_attribute(klass)
  most_specific_domain_attribute(klass, dependent_attributes)
end

#dependent_types<Class> Also known as: dependents

Returns this class’s dependent types.

Returns:

  • (<Class>)

    this class’s dependent types



83
84
85
# File 'lib/jinx/metadata/dependency.rb', line 83

def dependent_types
  dependent_properties.wrap { |dp| dp.type }
end

#depends_on?(other, recursive = false) ⇒ Boolean

Returns whether this class depends on the other class.

Parameters:

  • other (Class)

    the class to check

  • recursive (Boolean) (defaults to: false)

    whether to check if this class depends on a dependent of the other class

Returns:

  • (Boolean)

    whether this class depends on the other class



55
56
57
58
59
# File 'lib/jinx/metadata/dependency.rb', line 55

def depends_on?(other, recursive=false)
  owners.detect do |owner|
    other <= owner or (recursive and depends_on_recursive?(owner, other))
  end
end

#depends_on_recursive?(klass, other) ⇒ Boolean (private)

Returns whether the owner class depends on the other class.

Parameters:

  • klass (Class)

    the class to check

  • recursive (Boolean)

    whether to check whether this class depends on a dependent of the other class

Returns:

  • (Boolean)

    whether the owner class depends on the other class



232
233
234
# File 'lib/jinx/metadata/dependency.rb', line 232

def depends_on_recursive?(klass, other)
  klass != self and klass.owners.any? { |owner| owner.depends_on?(other, true) }
end

#detect_owner_attribute(klass, inverse) ⇒ Symbol? (private)

Returns this class’s owner attribute.

Parameters:

  • inverse (Symbol)

    the owner -> dependent attribute

Returns:

  • (Symbol, nil)

    this class’s owner attribute



269
270
271
272
273
274
275
276
277
# File 'lib/jinx/metadata/dependency.rb', line 269

def detect_owner_attribute(klass, inverse)
  ia = klass.property(inverse).inverse || detect_inverse_attribute(klass)
  if ia then
    logger.debug { "#{qp} reference to owner #{klass.qp} with inverse #{inverse} is #{ia}." }
  else
    logger.debug { "#{qp} reference to owner #{klass.qp} with inverse #{inverse} was not detected." }
  end
  ia
end

#local_owner_properties<Property> (private)

Returns the owner properties defined in this class.

Returns:

  • (<Property>)

    the owner properties defined in this class



252
253
254
# File 'lib/jinx/metadata/dependency.rb', line 252

def local_owner_properties
  @ops_local ||= []
end

#local_owner_property_hashObject (private)



241
242
243
# File 'lib/jinx/metadata/dependency.rb', line 241

def local_owner_property_hash
  @local_oa_hash ||= {}
end

#order_owner_attributes(*attributes) ⇒ Object (private)

Parameters:

  • attributes (<Symbol>)

    the order in which the effective owner attribute should be determined



237
238
239
# File 'lib/jinx/metadata/dependency.rb', line 237

def order_owner_attributes(*attributes)
  @ops = @ops_local = attributes.map { |oa| property(oa) }
end

#owner_attributeSymbol?

Returns the sole owner attribute of this class, or nil if there is not exactly one owner.

Returns:

  • (Symbol, nil)

    the sole owner attribute of this class, or nil if there is not exactly one owner



122
123
124
125
# File 'lib/jinx/metadata/dependency.rb', line 122

def owner_attribute
  prop = owner_property || return
  prop.attribute
end

#owner_properties<Property>

Returns the owner properties.

Returns:



68
69
70
# File 'lib/jinx/metadata/dependency.rb', line 68

def owner_properties
  @ops ||= create_owner_properties_enumerator
end

#owner_propertyProperty?

Returns the sole owner attribute metadata of this class, or nil if there is not exactly one owner.

Returns:

  • (Property, nil)

    the sole owner attribute metadata of this class, or nil if there is not exactly one owner



115
116
117
118
# File 'lib/jinx/metadata/dependency.rb', line 115

def owner_property
  props = owner_properties
  props.first if props.size == 1
end

#owner_property_hash{Class => Property} (protected)

Returns this class’s owner type => property hash.

Returns:

  • ({Class => Property})

    this class’s owner type => property hash



222
223
224
# File 'lib/jinx/metadata/dependency.rb', line 222

def owner_property_hash
  @op_hash ||= create_owner_property_hash
end

#owner_typeClass?

Returns the sole owner type of this class, or nil if there is not exactly one owner.

Returns:

  • (Class, nil)

    the sole owner type of this class, or nil if there is not exactly one owner



129
130
131
132
# File 'lib/jinx/metadata/dependency.rb', line 129

def owner_type
  prop = owner_property || return
  prop.type
end

#owner_types<Class> Also known as: owners

Returns this class’s owner types.

Returns:

  • (<Class>)

    this class’s owner types



107
108
109
# File 'lib/jinx/metadata/dependency.rb', line 107

def owner_types
  @owners ||= Enumerable::Enumerator.new(owner_property_hash, :each_key)
end