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



10
11
12
# File 'lib/jinx/metadata/dependency.rb', line 10

def owner_attributes
  @owner_attributes
end

#owners<Class> (readonly)

Returns this class’s owner types.

Returns:

  • (<Class>)

    this class’s owner types



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

def owners
  @owners
end

Instance Method Details

#add_dependent_attribute(attribute, *flags) ⇒ Object

Adds the given attribute as a dependent.

If the attribute inverse is not a collection, then the attribute 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 attribute’s declaring class

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

Parameters:

  • attribute (Symbol)

    the dependent to add

  • flags (<Symbol>)

    the attribute qualifier flags



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

def add_dependent_attribute(attribute, *flags)
  prop = property(attribute)
  logger.debug { "Marking #{qp}.#{attribute} as a dependent attribute of type #{prop.type.qp}..." }
  flags << :dependent unless flags.include?(:dependent)
  prop.qualify(*flags)
  inverse = prop.inverse
  inv_type = prop.type
  # example: Parent.add_dependent_attribute(:children) with inverse :parent calls the following:
  #   Child.add_owner(Parent, :children, :parent)
  inv_type.add_owner(self, attribute, inverse)
  logger.debug { "Marked #{qp}.#{attribute} as a dependent attribute with inverse #{inv_type.qp}#{inverse}." }
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:



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

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)
  prop = property(attribute) if attribute
  # 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.
  local_owner_property_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 inverse #{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
  unless prop.owner? then prop.qualify(:owner) end

  # Redefine the writer method to warn when changing the owner.
  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



171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/jinx/metadata/dependency.rb', line 171

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_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



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

def bidirectional_dependent?
  dependent? and not owner_attributes.empty?
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



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

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:



211
212
213
214
# File 'lib/jinx/metadata/dependency.rb', line 211

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

#dependent?Boolean

Returns whether this class depends on an owner.

Returns:

  • (Boolean)

    whether this class depends on an owner



38
39
40
# File 'lib/jinx/metadata/dependency.rb', line 38

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



54
55
56
# File 'lib/jinx/metadata/dependency.rb', line 54

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

#dependents<Class>

Returns this class’s dependent types.

Returns:

  • (<Class>)

    this class’s dependent types



74
75
76
# File 'lib/jinx/metadata/dependency.rb', line 74

def dependents
  dependent_attributes.wrap { |da| da.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



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

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



197
198
199
# File 'lib/jinx/metadata/dependency.rb', line 197

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



234
235
236
237
238
239
240
241
242
# File 'lib/jinx/metadata/dependency.rb', line 234

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



217
218
219
# File 'lib/jinx/metadata/dependency.rb', line 217

def local_owner_properties
  @ops_local ||= []
end

#local_owner_property_hashObject (private)



206
207
208
# File 'lib/jinx/metadata/dependency.rb', line 206

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



202
203
204
# File 'lib/jinx/metadata/dependency.rb', line 202

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



92
93
94
95
# File 'lib/jinx/metadata/dependency.rb', line 92

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

#owner_properties<Property>

Returns the owner properties.

Returns:



59
60
61
# File 'lib/jinx/metadata/dependency.rb', line 59

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



85
86
87
88
# File 'lib/jinx/metadata/dependency.rb', line 85

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



187
188
189
# File 'lib/jinx/metadata/dependency.rb', line 187

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



99
100
101
102
# File 'lib/jinx/metadata/dependency.rb', line 99

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