Class: Jinx::Property

Inherits:
Object show all
Includes:
PropertyCharacteristics
Defined in:
lib/jinx/metadata/property.rb

Overview

A Property captures the following metadata about a domain class attribute:

  • attribute symbol

  • declarer type

  • return type

  • reader method symbol

  • writer method symbol

Direct Known Subclasses

JavaProperty

Constant Summary collapse

SUPPORTED_FLAGS =

The supported property qualifier flags. See the complementary methods for an explanation of the flag option, e.g. Jinx::PropertyCharacteristics#dependent? for the :dependent flag.

Included persistence adapters should add specialized flags to this set. An unsupported flag is allowed and can be used by adapters, but a warning log message is issued in that case.

[
:collection, :dependent, :disjoint, :owner, :mandatory, :optional].to_set

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from PropertyCharacteristics

#bidirectional?, #bidirectional_java_association?, #collection?, #dependent?, #derived?, #disjoint?, #domain?, #independent?, #java_property?, #mandatory?, #many_to_many?, #nondomain?, #owner?, #unidirectional?, #unidirectional_java_dependent?

Constructor Details

#initialize(attribute, declarer, type = nil, *flags) ⇒ Property

Creates a new Property from the given attribute.

The return type is the referenced entity type. An attribute whose return type is a collection of domain objects is thus the domain object class rather than a collection class.

Parameters:

  • pa (String, Symbol)

    the subject attribute

  • declarer (Class)

    the declaring class

  • type (Class) (defaults to: nil)

    the return type

  • flags (<Symbol>)

    the qualifying #flags



51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/jinx/metadata/property.rb', line 51

def initialize(attribute, declarer, type=nil, *flags)
  # the attribute symbol
  @attribute = attribute.to_sym
  # the declaring class
  @declarer = declarer
  # the Ruby class
  @type = Class.to_ruby(type) if type
  # the read and write methods
  @accessors = [@attribute, "#{attribute}=".to_sym]
  # the qualifier flags
  @flags = Set.new
  qualify(*flags)
end

Instance Attribute Details

#accessors(Symbol, Symbol) (readonly)

Returns the standard attribute reader and writer methods.

Returns:

  • ((Symbol, Symbol))

    the standard attribute reader and writer methods



29
30
31
# File 'lib/jinx/metadata/property.rb', line 29

def accessors
  @accessors
end

#attributeSymbol (readonly) Also known as: to_sym

Returns the standard attribute symbol for this property.

Returns:

  • (Symbol)

    the standard attribute symbol for this property



26
27
28
# File 'lib/jinx/metadata/property.rb', line 26

def attribute
  @attribute
end

#declarerClass (readonly)

Returns the declaring class.

Returns:

  • (Class)

    the declaring class



32
33
34
# File 'lib/jinx/metadata/property.rb', line 32

def declarer
  @declarer
end

#flags<Symbol> (readonly)

Returns the qualifier flags.

Returns:

  • (<Symbol>)

    the qualifier flags

See Also:



39
40
41
# File 'lib/jinx/metadata/property.rb', line 39

def flags
  @flags
end

#typeClass

Returns the return type.

Returns:

  • (Class)

    the return type



35
36
37
# File 'lib/jinx/metadata/property.rb', line 35

def type
  @type
end

Instance Method Details

#clear_inverseObject (private)



254
255
256
257
258
259
260
261
262
263
264
# File 'lib/jinx/metadata/property.rb', line 254

def clear_inverse
  return unless @inv_prop
  logger.debug { "Clearing #{@declarer.qp}.#{self} inverse #{type.qp}.#{inverse}..." }
  # Capture the inverse before unsetting it.
  ip = @inv_prop
  # Unset the inverse.
  @inv_prop = nil
  # Clear the inverse of the inverse.
  ip.inverse = nil
  logger.debug { "Cleared #{@declarer.qp}.#{self} inverse." }
end

#deep_copyProperty (private)

Creates a copy of this metadata which does not share mutable content.

The copy instance variables are as follows:

  • the copy inverse and restrictions are empty

  • the copy flags is a deep copy of this attribute’s flags

  • other instance variable references are shared between the copy and this attribute

Returns:



248
249
250
251
252
# File 'lib/jinx/metadata/property.rb', line 248

def deep_copy
  other = dup
  other.dup_content
  other
end

#dependent_flag_setObject (private)

Validates that this is not an owner attribute.

Raises:



305
306
307
308
309
# File 'lib/jinx/metadata/property.rb', line 305

def dependent_flag_set
  if owner? then
    raise MetadataError.new("#{declarer.qp}.#{self} cannot be set as a #{type.qp} dependent since it is already defined as a #{type.qp} owner")
  end
end

#derived_inverseBoolean

Returns this attribute’s inverse attribute if the inverse is a derived attribute, or nil otherwise.

Returns:

  • (Boolean)

    this attribute’s inverse attribute if the inverse is a derived attribute, or nil otherwise



146
147
148
# File 'lib/jinx/metadata/property.rb', line 146

def derived_inverse
  @inv_prop.attribute if @inv_prop and @inv_prop.derived?
end

#dup_contentObject (protected)

Duplicates the mutable content as part of a #deep_copy.



210
211
212
213
214
215
# File 'lib/jinx/metadata/property.rb', line 210

def dup_content
  # keep the copied flags but don't share them
  @flags = @flags.dup
  # restrictions and inverse are neither shared nor copied
  @inv_prop = @restrictions = nil
end

#flag_supported?(flag) ⇒ Boolean (private)

Returns whether the flag is supported.

Parameters:

  • the (Symbol)

    flag to set

Returns:

  • (Boolean)

    whether the flag is supported



236
237
238
# File 'lib/jinx/metadata/property.rb', line 236

def flag_supported?(flag)
  SUPPORTED_FLAGS.include?(flag)
end

#inverseSymbol?

Returns the inverse of this attribute, if any.

Returns:

  • (Symbol, nil)

    the inverse of this attribute, if any



76
77
78
# File 'lib/jinx/metadata/property.rb', line 76

def inverse
  @inv_prop.attribute if @inv_prop
end

#inverse=(attribute) ⇒ Object

Sets the inverse of the subject attribute to the given attribute. The inverse relation is symmetric, i.e. the inverse of the referenced Property is set to this Property’s subject attribute.

Parameters:

  • attribute (Symbol, nil)

    the inverse attribute

Raises:

  • (MetadataError)

    if the the inverse of the inverse is already set to a different attribute



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/jinx/metadata/property.rb', line 107

def inverse=(attribute)
  return if inverse == attribute
  # if no attribute, then the clear the existing inverse, if any
  return clear_inverse if attribute.nil?
  # the inverse attribute meta-data
  begin
    @inv_prop = type.property(attribute)
  rescue NameError => e
    raise MetadataError.new("#{@declarer.qp}.#{self} inverse attribute #{type.qp}.#{attribute} not found")
  end
  # the inverse of the inverse
  inv_inv_prop = @inv_prop.inverse_property
  # If the inverse of the inverse is already set to a different attribute, then raise an exception.
  if inv_inv_prop and not (inv_inv_prop == self or inv_inv_prop.restriction?(self))
    raise MetadataError.new("Cannot set #{type.qp}.#{attribute} inverse attribute to #{@declarer.qp}.#{self}@#{object_id} since it conflicts with existing inverse #{inv_inv_prop.declarer.qp}.#{inv_inv_prop}@#{inv_inv_prop.object_id}")
  end
  # Set the inverse of the inverse to this attribute.
  @inv_prop.inverse = @attribute
  # If this attribute is disjoint, then so is the inverse.
  @inv_prop.qualify(:disjoint) if disjoint?
  logger.debug { "Assigned #{@declarer.qp}.#{self} attribute inverse to #{type.qp}.#{attribute}." }
end

#inverse_propertyProperty?

Returns the property for the #inverse attribute, if any.

Returns:



131
132
133
# File 'lib/jinx/metadata/property.rb', line 131

def inverse_property
  @inv_prop
end

#owner_flag_setObject (private)

This method is called when the owner flag is set. The inverse is inferred as the referenced owner type’s dependent attribute which references this attribute’s type.

Raises:

  • (MetadataError)

    if this attribute is dependent or an inverse could not be inferred



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/jinx/metadata/property.rb', line 285

def owner_flag_set
  if dependent? then
    raise MetadataError.new("#{declarer.qp}.#{self} cannot be set as a #{type.qp} owner since it is already defined as a #{type.qp} dependent")
  end
  inv_prop = inverse_property
  if inv_prop then
    inv_prop.qualify(:dependent) unless inv_prop.dependent?
  else
    inv_attr = type.dependent_attribute(@declarer)
    if inv_attr.nil? then
      raise MetadataError.new("The #{@declarer.qp} owner attribute #{self} of type #{type.qp} does not have an inverse")
    end
    logger.debug { "#{declarer.qp}.#{self} inverse is the #{type.qp} dependent attribute #{inv_attr}." }
    self.inverse = inv_attr
  end
end

#qualify(*flags) ⇒ Object

Qualifies this attribute with the given flags. Supported flags are listed in SUPPORTED_FLAGS.

Parameters:

  • the (<Symbol>)

    flags to add

Raises:

  • (ArgumentError)

    if the flag is not supported



139
140
141
142
143
# File 'lib/jinx/metadata/property.rb', line 139

def qualify(*flags)
  flags.each { |flag| set_flag(flag) }
  # propagate to restrictions
  if @restrictions then @restrictions.each { |prop| prop.qualify(*flags) } end
end

#readerSymbol

Returns the reader method.

Returns:

  • (Symbol)

    the reader method



66
67
68
# File 'lib/jinx/metadata/property.rb', line 66

def reader
  accessors.first
end

#restrict(declarer, opts = {}) ⇒ Property

Creates a new declarer attribute which restricts this attribute. This method should only be called by a Resource class, since the class is responsible for resetting the attribute symbol => meta-data association to point to the new restricted attribute.

If this attribute has an inverse, then the restriction inverse is set to the attribute declared by the restriction declarer’. For example, if:

  • AbstractProtocol.coordinator has inverse Administrator.protocol

  • AbstractProtocol has subclass StudyProtocol

  • StudyProtocol.coordinator returns a StudyCoordinator

  • AbstractProtocol.coordinator is restricted to StudyProtocol

then calling this method on the StudyProtocol.coordinator restriction sets the StudyProtocol.coordinator inverse to StudyCoordinator.coordinator.

Parameters:

  • declarer (Class)

    the subclass which declares the new restricted attribute

  • opts (Hash, nil) (defaults to: {})

    the restriction options

Options Hash (opts):

  • type (Class)

    the restriction return type (default this attribute’s return type)

  • type (Symbol)

    the restriction inverse (default this attribute’s inverse)

Returns:

  • (Property)

    the new restricted attribute

Raises:

  • (ArgumentError)

    if the restricted declarer is not a subclass of this attribute’s declarer

  • (ArgumentError)

    if there is a restricted return type and it is not a subclass of this attribute’s return type

  • (MetadataError)

    if this attribute has an inverse that is not independently declared by the restricted declarer subclass



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/jinx/metadata/property.rb', line 174

def restrict(declarer, opts={})
  rtype = opts[:type] || @type
  rinv = opts[:inverse] || inverse
  unless declarer < @declarer then
    raise ArgumentError.new("Cannot restrict #{@declarer.qp}.#{self} to an incompatible declarer type #{declarer.qp}")
  end
  unless rtype <= @type then
    raise ArgumentError.new("Cannot restrict #{@declarer.qp}.#{self}({@type.qp}) to an incompatible return type #{rtype.qp}")
  end
  # Copy this attribute and its instance variables minus the restrictions and make a deep copy of the flags.
  rst = deep_copy
  # specialize the copy declarer
  rst.set_restricted_declarer(declarer)
  # Capture the restriction to propagate modifications to this metadata, esp. adding an inverse.
  @restrictions ||= []
  @restrictions << rst
  # Set the restriction type
  rst.type = rtype
  # Specialize the inverse to the restricted type attribute, if necessary.
  rst.inverse = rinv
  rst
end

#restrict_flags(declarer, *flags) ⇒ Property

Creates a new declarer attribute which qualifies this attribute for the given declarer.

Parameters:

  • flags (<Symbol>)

    the additional flags for the restricted attribute

  • declarer (Class)

    the subclass which declares the new restricted attribute

Returns:

  • (Property)

    the new restricted attribute



95
96
97
98
99
# File 'lib/jinx/metadata/property.rb', line 95

def restrict_flags(declarer, *flags)
  copy = restrict(declarer)
  copy.qualify(*flags)
  copy
end

#restriction?(other) ⇒ Boolean (protected)

Returns whether the other attribute restricts this attribute.

Parameters:

  • other (Property)

    the other attribute to check

Returns:

  • (Boolean)

    whether the other attribute restricts this attribute



219
220
221
# File 'lib/jinx/metadata/property.rb', line 219

def restriction?(other)
  @restrictions and @restrictions.include?(other)
end

#set_flag(flag) ⇒ Object (private)

Parameters:

  • the (Symbol)

    flag to set

Raises:

  • (ArgumentError)

    if the flag is not supported



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

def set_flag(flag)
  return if @flags.include?(flag)
  unless flag_supported?(flag) then
    raise ArgumentError.new("Property #{declarer.name}.#{self} flag not supported: #{flag.qp}")
  end
  @flags << flag
  case flag
  when :owner then owner_flag_set
  when :dependent then dependent_flag_set
  end
end

#set_restricted_declarer(klass) ⇒ Object (protected)

Parameters:

  • klass (Class)

    the declaring class of this restriction attribute



224
225
226
227
228
229
230
# File 'lib/jinx/metadata/property.rb', line 224

def set_restricted_declarer(klass)
  if @declarer and not klass < @declarer then
    raise MetadataError.new("Cannot reset #{declarer.qp}.#{self} declarer to #{type.qp}")
  end
  @declarer = klass
  @declarer.add_restriction(self)
end

#to_sObject Also known as: inspect, qp



199
200
201
# File 'lib/jinx/metadata/property.rb', line 199

def to_s
  attribute.to_s
end

#writerSymbol

Returns the writer method.

Returns:

  • (Symbol)

    the writer method



71
72
73
# File 'lib/jinx/metadata/property.rb', line 71

def writer
  accessors.last
end