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. #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

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

#bidirectional?Boolean

Returns whether this property has an inverse.

Returns:

  • (Boolean)

    whether this property has an inverse



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

def bidirectional?
  !!@inv_prop
end

#bidirectional_java_association?Boolean

Returns whether this is a Java attribute which has a Java inverse.

Returns:

  • (Boolean)

    whether this is a Java attribute which has a Java inverse



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

def bidirectional_java_association?
  inverse and java_property? and inverse_property.java_property?
end

#clear_inverseObject (private)



349
350
351
352
353
354
355
356
357
358
359
# File 'lib/jinx/metadata/property.rb', line 349

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

#collection?Boolean

Returns whether the subject attribute return type is a collection.

Returns:

  • (Boolean)

    whether the subject attribute return type is a collection



176
177
178
# File 'lib/jinx/metadata/property.rb', line 176

def collection?
  @flags.include?(:collection)
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:



343
344
345
346
347
# File 'lib/jinx/metadata/property.rb', line 343

def deep_copy
  other = dup
  other.dup_content
  other
end

#dependent?Boolean

Returns whether the subject attribute is a dependent on a parent. See the Jinx configuration documentation for a dependency description.

Returns:

  • (Boolean)

    whether the attribute references a dependent



184
185
186
# File 'lib/jinx/metadata/property.rb', line 184

def dependent?
  @flags.include?(:dependent)
end

#dependent_flag_setObject (private)

Validates that this is not an owner attribute.

Raises:



395
396
397
398
399
# File 'lib/jinx/metadata/property.rb', line 395

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?Boolean

An attribute is derived if the attribute value is set by setting another attribute, e.g. if this attribute is the inverse of a dependent owner attribute.

Returns:

  • (Boolean)

    whether this attribute is derived from another attribute



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

def derived?
  dependent? and !!inverse
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



204
205
206
# File 'lib/jinx/metadata/property.rb', line 204

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

#disjoint?Boolean

Returns whether this is a dependent attribute which has exactly one owner value chosen from several owner attributes.

Returns:

  • (Boolean)

    whether this is a dependent attribute which has exactly one owner value chosen from several owner attributes



230
231
232
# File 'lib/jinx/metadata/property.rb', line 230

def disjoint?
  @flags.include?(:disjoint)
end

#domain?Boolean

Returns whether the subject attribute returns a domain object or collection of domain objects.

Returns:

  • (Boolean)

    whether the subject attribute returns a domain object or collection of domain objects



165
166
167
168
# File 'lib/jinx/metadata/property.rb', line 165

def domain?
  # the type must be a Ruby class rather than a Java Class, and include the Domain mix-in
  Class === type and type < Resource
end

#dup_contentObject (protected)

Duplicates the mutable content as part of a #deep_copy.



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

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



331
332
333
# File 'lib/jinx/metadata/property.rb', line 331

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

#independent?Boolean

An independent attribute is a reference to one or more non-dependent Resource objects. An #owner? attribute is independent.

Returns:

  • (Boolean)

    whether the subject attribute is a non-dependent domain attribute



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

def independent?
  domain? and not dependent?
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



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/jinx/metadata/property.rb', line 116

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:



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

def inverse_property
  @inv_prop
end

#java_property?Boolean

Returns whether the subject attribute encapsulates a Java attribute.

Returns:

  • (Boolean)

    whether the subject attribute encapsulates a Java attribute



160
161
162
# File 'lib/jinx/metadata/property.rb', line 160

def java_property?
  JavaProperty === self
end

#mandatory?Boolean

Returns whether the subject attribute must have a value when it is saved

Returns:

  • (Boolean)

    whether the attribute is mandatory



191
192
193
# File 'lib/jinx/metadata/property.rb', line 191

def mandatory?
  @declarer.mandatory_attributes.include?(attribute)
end

#many_to_many?Boolean

Returns whether this attribute is a collection with a collection inverse.

Returns:

  • (Boolean)

    whether this attribute is a collection with a collection inverse



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

def many_to_many?
  return false unless collection?
  inv_prop = inverse_property
  inv_prop and inv_prop.collection?
end

#nondomain?Boolean

Returns whether the subject attribute is not a domain object attribute.

Returns:

  • (Boolean)

    whether the subject attribute is not a domain object attribute



171
172
173
# File 'lib/jinx/metadata/property.rb', line 171

def nondomain?
  not domain?
end

#owner?Boolean

Returns whether the subject attribute is a dependency owner.

Returns:

  • (Boolean)

    whether the subject attribute is a dependency owner



224
225
226
# File 'lib/jinx/metadata/property.rb', line 224

def owner?
  @flags.include?(:owner)
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



380
381
382
383
384
385
386
387
388
389
390
# File 'lib/jinx/metadata/property.rb', line 380

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_attr = type.dependent_attribute(@declarer)
  if inv_attr.nil? then
    raise MetadataError.new("#{@declarer.qp} owner attribute #{self} does not have a #{type.qp} dependent inverse")
  end
  logger.debug { "#{declarer.qp}.#{self} inverse is the #{type.qp} dependent attribute #{inv_attr}." }
  self.inverse = inv_attr
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



153
154
155
156
157
# File 'lib/jinx/metadata/property.rb', line 153

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



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/jinx/metadata/property.rb', line 269

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



104
105
106
107
108
# File 'lib/jinx/metadata/property.rb', line 104

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



314
315
316
# File 'lib/jinx/metadata/property.rb', line 314

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



363
364
365
366
367
368
369
370
371
372
373
# File 'lib/jinx/metadata/property.rb', line 363

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



319
320
321
322
323
324
325
# File 'lib/jinx/metadata/property.rb', line 319

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



294
295
296
# File 'lib/jinx/metadata/property.rb', line 294

def to_s
  attribute.to_s
end

#unidirectional?Boolean

An attribute is unidirectional if both of the following is true:

  • there is no distinct #inverse attribute

  • the attribute is not a #dependent? with more than one owner

Returns:

  • (Boolean)

    whether this attribute does not have an inverse



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

def unidirectional?
  inverse.nil? and not (dependent? and type.owner_attributes.size > 1)
end

#unidirectional_java_dependent?Boolean

Returns whether this attribute is a dependent which does not have a Java inverse owner attribute.

Returns:

  • (Boolean)

    whether this attribute is a dependent which does not have a Java inverse owner attribute



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

def unidirectional_java_dependent?
  dependent? and java_property? and not bidirectional_java_association?
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