caruby-core 1.4.9 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/History.md +48 -0
  2. data/lib/caruby/cli/command.rb +2 -1
  3. data/lib/caruby/csv/csv_mapper.rb +8 -8
  4. data/lib/caruby/database/persistable.rb +44 -65
  5. data/lib/caruby/database/persistence_service.rb +12 -9
  6. data/lib/caruby/database/persistifier.rb +14 -14
  7. data/lib/caruby/database/reader.rb +53 -51
  8. data/lib/caruby/database/search_template_builder.rb +9 -10
  9. data/lib/caruby/database/store_template_builder.rb +58 -58
  10. data/lib/caruby/database/writer.rb +96 -96
  11. data/lib/caruby/database.rb +19 -19
  12. data/lib/caruby/domain/attribute.rb +581 -0
  13. data/lib/caruby/domain/attributes.rb +615 -0
  14. data/lib/caruby/domain/dependency.rb +240 -0
  15. data/lib/caruby/domain/importer.rb +183 -0
  16. data/lib/caruby/domain/introspection.rb +176 -0
  17. data/lib/caruby/domain/inverse.rb +173 -0
  18. data/lib/caruby/domain/inversible.rb +1 -2
  19. data/lib/caruby/domain/java_attribute.rb +173 -0
  20. data/lib/caruby/domain/merge.rb +13 -10
  21. data/lib/caruby/domain/metadata.rb +141 -0
  22. data/lib/caruby/domain/mixin.rb +35 -0
  23. data/lib/caruby/domain/reference_visitor.rb +5 -3
  24. data/lib/caruby/domain.rb +340 -0
  25. data/lib/caruby/import/java.rb +29 -25
  26. data/lib/caruby/migration/migratable.rb +5 -5
  27. data/lib/caruby/migration/migrator.rb +19 -15
  28. data/lib/caruby/migration/resource_module.rb +1 -1
  29. data/lib/caruby/resource.rb +39 -30
  30. data/lib/caruby/util/collection.rb +94 -33
  31. data/lib/caruby/util/coordinate.rb +28 -2
  32. data/lib/caruby/util/log.rb +4 -4
  33. data/lib/caruby/util/module.rb +12 -28
  34. data/lib/caruby/util/partial_order.rb +9 -10
  35. data/lib/caruby/util/pretty_print.rb +46 -26
  36. data/lib/caruby/util/topological_sync_enumerator.rb +10 -4
  37. data/lib/caruby/util/transitive_closure.rb +2 -2
  38. data/lib/caruby/util/visitor.rb +1 -1
  39. data/lib/caruby/version.rb +1 -1
  40. data/test/lib/caruby/database/persistable_test.rb +1 -1
  41. data/test/lib/caruby/domain/domain_test.rb +14 -28
  42. data/test/lib/caruby/domain/inversible_test.rb +1 -1
  43. data/test/lib/caruby/import/java_test.rb +5 -0
  44. data/test/lib/caruby/migration/test_case.rb +0 -1
  45. data/test/lib/caruby/test_case.rb +9 -10
  46. data/test/lib/caruby/util/collection_test.rb +23 -5
  47. data/test/lib/caruby/util/module_test.rb +10 -14
  48. data/test/lib/caruby/util/partial_order_test.rb +16 -15
  49. data/test/lib/caruby/util/visitor_test.rb +1 -1
  50. data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +1 -1
  51. metadata +16 -15
  52. data/History.txt +0 -44
  53. data/lib/caruby/domain/attribute_metadata.rb +0 -551
  54. data/lib/caruby/domain/java_attribute_metadata.rb +0 -183
  55. data/lib/caruby/domain/resource_attributes.rb +0 -565
  56. data/lib/caruby/domain/resource_dependency.rb +0 -217
  57. data/lib/caruby/domain/resource_introspection.rb +0 -160
  58. data/lib/caruby/domain/resource_inverse.rb +0 -151
  59. data/lib/caruby/domain/resource_metadata.rb +0 -155
  60. data/lib/caruby/domain/resource_module.rb +0 -370
  61. data/lib/caruby/yard/resource_metadata_handler.rb +0 -8
@@ -0,0 +1,581 @@
1
+ require 'set'
2
+ require 'caruby/util/inflector'
3
+ require 'caruby/util/collection'
4
+ require 'caruby/util/validation'
5
+ require 'caruby/domain/java_attribute'
6
+
7
+ module CaRuby
8
+ module Domain
9
+ # An Attribute captures the following metadata about a domain class attribute:
10
+ # * attribute symbol
11
+ # * declarer type
12
+ # * return type
13
+ # * reader method symbol
14
+ # * writer method symbol
15
+ class Attribute
16
+ # The supported attribute qualifier flags. See the complementary methods for an explanation of
17
+ # the flag option, e.g. {#autogenerated?} for the +:autogenerated+ flag.
18
+ SUPPORTED_FLAGS = [
19
+ :autogenerated, :autogenerated_on_update, :collection, :dependent, :derived, :logical, :disjoint,
20
+ :owner, :cascaded, :no_cascade_update_to_create, :saved, :unsaved, :optional, :fetched, :unfetched,
21
+ :include_in_save_template, :saved_fetch, :create_only, :update_only, :unidirectional, :volatile].to_set
22
+
23
+ # @return [(Symbol, Symbol)] the standard attribute reader and writer methods
24
+ attr_reader :accessors
25
+
26
+ # @return [Class] the declaring class
27
+ attr_accessor :declarer
28
+
29
+ # @return [Class] the return type
30
+ attr_reader :type
31
+
32
+ # @return [<Symbol>] the qualifier flags
33
+ # @see SUPPORTED_FLAGS
34
+ attr_accessor :flags
35
+
36
+ # Creates a new Attribute from the given attribute.
37
+ #
38
+ # The return type is the referenced entity type. An attribute whose return type is a
39
+ # collection of domain objects is thus the domain object class rather than a collection class.
40
+ #
41
+ # @param [String,Symbol] attr the subject attribute
42
+ # @param [Class] declarer the declaring class
43
+ # @param [Class] type the return type
44
+ # @param [<Symbol>] flags the qualifying flags
45
+ # @option flags :dependent the attribute references a dependent
46
+ # @option flags :collection the attribute return type is a collection
47
+ # @option flags :owner the attribute references the owner of a dependent
48
+ # @option flags :cascaded database create/update/delete operation propagates to the attribute reference
49
+ def initialize(attribute, declarer, type=nil, *flags)
50
+ # the attribute symbol
51
+ @symbol = attribute.to_sym
52
+ # the declaring class
53
+ @declarer = declarer
54
+ # the Ruby class
55
+ @type = Class.to_ruby(type) if type
56
+ # the read and write methods
57
+ @accessors = [@symbol, "#{attribute}=".to_sym]
58
+ # the qualifier flags
59
+ @flags = Set.new
60
+ # identifier is always volatile
61
+ if @symbol == :identifier then flags << :volatile end
62
+ qualify(*flags)
63
+ end
64
+
65
+ # @return [Symbol] the reader method
66
+ def reader
67
+ accessors.first
68
+ end
69
+
70
+ # @return [Symbol] the writer method
71
+ def writer
72
+ accessors.last
73
+ end
74
+
75
+ # @return [Symbol, nil] the inverse of this attribute, if any
76
+ def inverse
77
+ @inv_md.to_sym if @inv_md
78
+ end
79
+
80
+ # An attribute is unidirectional if both of the following is true:
81
+ # * there is no distinct {#inverse} attribute
82
+ # * the attribute is not a {#dependent?} with more than one owner
83
+ #
84
+ # @return [Boolean] whether this attribute does not have an inverse
85
+ def unidirectional?
86
+ inverse.nil? and not (dependent? and type.owner_attributes.size > 1)
87
+ end
88
+
89
+ # @param [Class] the attribute return type
90
+ def type=(klass)
91
+ return if klass == @type
92
+ @type = klass
93
+ if @inv_md then
94
+ self.inverse = @inv_md.to_sym
95
+ logger.debug { "Reset #{@declarer.qp}.#{self} inverse from #{@inv_md.type}.#{@inv_md} to #{klass}#{@inv_md}." }
96
+ end
97
+ end
98
+
99
+ # Creates a new declarer attribute which restricts this attribute {#type} to the given type.
100
+ #
101
+ # @param declarer (see #restrict)
102
+ # @param [Class] type the restricted subclass of this attribute's return type
103
+ # @return (see #restrict)
104
+ def restrict_type(declarer, type)
105
+ if self.type and not type < self.type then
106
+ raise ArgumentError.new("Cannot restrict #{self.declarer.qp}.#{self} to incompatible attribute type #{type.qp}")
107
+ end
108
+ rst = restrict(declarer)
109
+ rst.type = type
110
+ # specialize the inverse to the restricted type attribute, if necessary
111
+ rst.restrict_inverse_type
112
+ rst
113
+ end
114
+
115
+ # Creates a new declarer attribute which qualifies this attribute for the given declarer.
116
+ #
117
+ # @param declarer (see #restrict)
118
+ # @param [<Symbol>] flags the additional flags for the restricted attribute
119
+ # @return (see #restrict)
120
+ def restrict_flags(declarer, *flags)
121
+ copy = restrict(declarer)
122
+ copy.qualify(*flags)
123
+ copy
124
+ end
125
+
126
+ # Sets the inverse of the subject attribute to the given attribute.
127
+ # The inverse relation is symmetric, i.e. the inverse of the referenced Attribute
128
+ # is set to this Attribute's subject attribute.
129
+ #
130
+ # @param attribute the inverse attribute
131
+ def inverse=(attribute)
132
+ return if inverse == attribute
133
+ # if no attribute, then the clear the existing inverse, if any
134
+ return clear_inverse if attribute.nil?
135
+
136
+ # the inverse attribute meta-data
137
+ begin
138
+ @inv_md = type.attribute_metadata(attribute)
139
+ rescue NameError
140
+ raise MetadataError.new("#{@declarer.qp}.#{self} inverse attribute #{type.qp}.#{attribute} not found - #{$!}")
141
+ end
142
+ # the inverse of the inverse
143
+ inv_inv_md = @inv_md.inverse_metadata
144
+ # If the inverse of the inverse is already set to a different attribute, then raise an exception.
145
+ # Otherwise, it there is an inverse, then set the inverse of the inverse to this attribute.
146
+ return if inv_inv_md == self
147
+ if inv_inv_md and not inv_inv_md.restriction?(self) then
148
+ raise MetadataError.new("Cannot set #{type.qp}.#{@inv_md} inverse attribute to #{@declarer.qp}.#{self} since it conflicts with existing inverse #{inv_inv_md.declarer.qp}.#{inv_inv_md}")
149
+ end
150
+ # set the inverse of the inverse
151
+ @inv_md.inverse = @symbol
152
+ # If this attribute is disjoint, then so is the inverse.
153
+ @inv_md.qualify(:disjoint) if disjoint?
154
+
155
+ # propagate to restrictions
156
+ # if @restrictions then
157
+ # @restrictions.each { |attr_md| attr_md.restrict_inverse_type(@inv_md) }
158
+ # end
159
+ logger.debug { "Set #{@declarer.qp}.#{self} inverse to #{type.qp}.#{attribute}." }
160
+ end
161
+
162
+ # @return [Attribute, nil] the metadata for the {#inverse} attribute, if any
163
+ def inverse_metadata
164
+ @inv_md
165
+ end
166
+
167
+ # Qualifies this attribute with the given flags. Supported flags are listed in {SUPPORTED_FLAGS}.
168
+ #
169
+ # @param [<Symbol>] the flags to add
170
+ # @raise [ArgumentError] if the flag is not supported
171
+ def qualify(*flags)
172
+ flags.each { |flag| set_flag(flag) }
173
+ # propagate to restrictions
174
+ if @restrictions then @restrictions.each { |attr_md| attr_md.qualify(*flags) } end
175
+ end
176
+
177
+ # @return whether the subject attribute encapsulates a Java property
178
+ def java_property?
179
+ JavaAttribute === self
180
+ end
181
+
182
+ # @return whether the subject attribute returns a domain object or collection of domain objects
183
+ def domain?
184
+ # the type must be a Ruby class rather than a Java Class, and include the Domain mix-in
185
+ Class === type and type < Resource
186
+ end
187
+
188
+ # @return whether the subject attribute is not a domain object attribute
189
+ def nondomain?
190
+ not domain?
191
+ end
192
+
193
+ # Returns whether the subject attribute is fetched, determined as follows:
194
+ # * An attribute marked with the :fetched flag is fetched.
195
+ # * An attribute marked with the :unfetched flag is not fetched.
196
+ # Otherwise, a non-domain attribute is fetched, and a domain attribute is
197
+ # fetched if one of the following conditions hold:
198
+ # * A dependent domain attribute is fetched if it is not logical.
199
+ # * An owner domain attribute is fetched by default.
200
+ # * An independent domain attribute is fetched if it is abstract and not derived.
201
+ #
202
+ # @return [Boolean] whether the attribute is fetched
203
+ def fetched?
204
+ return true if @flags.include?(:fetched)
205
+ return false if @flags.include?(:unfetched)
206
+ nondomain? or dependent? ? fetched_dependent? : fetched_independent?
207
+ end
208
+
209
+ # @return whether the subject attribute return type is a collection
210
+ def collection?
211
+ @flags.include?(:collection)
212
+ end
213
+
214
+ # Returns whether the subject attribute is a dependent on a parent. See the caRuby configuration
215
+ # documentation for a dependency description.
216
+ #
217
+ # @return [Boolean] whether the attribute references a dependent
218
+ def dependent?
219
+ @flags.include?(:dependent)
220
+ end
221
+
222
+ # Returns whether the subject attribute is marked as optional in a create.
223
+ # This method returns true only if the :optional flag is explicitly set.
224
+ # Other attributes are optional by default.
225
+ #
226
+ # @return [Boolean] whether the attribute is optional
227
+ # @see Attributes#mandatory_attributes.
228
+ def optional?
229
+ @flags.include?(:optional)
230
+ end
231
+
232
+ # Returns whether the subject attribute is not saved.
233
+ #
234
+ # @return [Boolean] whether the attribute is unsaved
235
+ def unsaved?
236
+ @flags.include?(:unsaved)
237
+ end
238
+
239
+ # Returns whether the subject attribute is a dependent whose value is automatically generated
240
+ # with place-holder domain objects when the parent is created. An attribute is auto-generated
241
+ # if the +:autogenerate+ or the +:autogenerated_on_update+ flag is set.
242
+ #
243
+ # @return [Boolean] whether the attribute is auto-generated
244
+ def autogenerated?
245
+ @flags.include?(:autogenerated) or @flags.include?(:autogenerated_on_update)
246
+ end
247
+
248
+ # Returns whether the the subject attribute is #{autogenerated?} for create. An attribute is
249
+ # auto-generated for create if the +:autogenerate+ flag is set and the
250
+ # +:autogenerated_on_update+ flag is not set.
251
+ #
252
+ # @return [Boolean] whether the attribute is auto-generated on create
253
+ def autogenerated_on_create?
254
+ @flags.include?(:autogenerated) and not @flags.include?(:autogenerated_on_update)
255
+ end
256
+
257
+ # Returns whether this attribute must be fetched when a declarer instance is saved.
258
+ # An attribute is a saved fetch attribute if either of the following conditions hold:
259
+ # * it is {#autogenerated?}
260
+ # * it is {#cascaded?} and marked with the +:unfetched+ flag.
261
+ #
262
+ # @return [Boolean] whether the subject attribute must be refetched in order to reflect
263
+ # the database content
264
+ def saved_fetch?
265
+ @flags.include?(:saved_fetch) or autogenerated? or (cascaded? and @flags.include?(:unfetched))
266
+ end
267
+
268
+ # Returns whether the subject attribute is a dependent whose owner does not automatically
269
+ # cascade application service creation or update to the dependent. It is incumbent upon
270
+ # CaRuby::Database to cascade the changes.
271
+ #
272
+ # @return [Boolean] whether the attribute is an uncascaded dependent
273
+ def logical?
274
+ @flags.include?(:logical)
275
+ end
276
+
277
+ # An attribute is derived if the attribute value is set by setting another attribute, e.g. if this
278
+ # attribute is the inverse of a dependent owner attribute.
279
+ #
280
+ # @return [Boolean] whether this attribute is derived from another attribute
281
+ def derived?
282
+ @flags.include?(:derived) or (dependent? and not inverse.nil?)
283
+ end
284
+
285
+ # @return [Boolean] this attribute's inverse attribute if the inverse is a derived attribute, or nil otherwise
286
+ def derived_inverse
287
+ @inv_md.to_sym if @inv_md and @inv_md.derived?
288
+ end
289
+
290
+ # An independent attribute is a reference to one or more non-dependent Resource objects.
291
+ # An {#owner?} attribute is independent.
292
+ #
293
+ # @return [Boolean] whether the subject attribute is a non-dependent domain attribute
294
+ def independent?
295
+ domain? and not dependent?
296
+ end
297
+
298
+ # A Java attribute is creatable if all of the following conditions hold:
299
+ # * the attribute is {#saved?}
300
+ # * the attribute is not a {#proxied_save?}
301
+ # * the attribute :update_only flag is not set
302
+ #
303
+ # @return [Boolean] whether this attribute is saved in a create operation
304
+ def creatable?
305
+ saved? and not @flags.include?(:update_only)
306
+ end
307
+
308
+ # A Java attribute is an uncreated dependent if any of the following conditions hold:
309
+ # * the attribute is a {#logical?} dependent
310
+ # * the attribute is a #dependent? which is not {#creatable?}
311
+ #
312
+ # @return [Boolean] whether this attribute is saved in a create operation
313
+ def uncreated_dependent?
314
+ logical? or (dependent? and not creatable?)
315
+ end
316
+
317
+ # A Java attribute is updatable if all of the following conditions hold:
318
+ # * the attribute is {#saved?}
319
+ # * the attribute :create_only flag is not set
320
+ #
321
+ # @return [Boolean] whether this attribute is saved in a update operation
322
+ def updatable?
323
+ saved? and not @flags.include?(:create_only)
324
+ end
325
+
326
+ # @return [Boolean] whether the attribute is a physical dependent or the +:cascaded+ flag is set
327
+ def cascaded?
328
+ (dependent? and not logical?) or @flags.include?(:cascaded)
329
+ end
330
+
331
+ # @return whether this attribute is {#cascaded?} or marked with the +:include_in_save_template+ flag
332
+ def include_in_save_template?
333
+ cascaded? or @flags.include?(:include_in_save_template)
334
+ end
335
+
336
+ # Returns whether this attribute is #{#cascaded} and cascades a parent update to a child
337
+ # create. This corresponds to the Hibernate +save-update+ cascade style but not the Hibernate
338
+ # +all+ cascade style.
339
+ #
340
+ # This method returns true if this attribute is cascaded and the +:no_cascade_update_to_create+
341
+ # flag is not set. Set this flag if the Hibernate mapping specifies the +all+ cascade style.
342
+ # Failure to set this flag will result in the caTissue Hibernate error:
343
+ # Exception: gov.nih.nci.system.applicationservice.ApplicationException:
344
+ # The given object has a null identifier:
345
+ # followed by the attribute type name.
346
+ #
347
+ # @return [Boolean] whether the attribute cascades to crate when the owner is updated
348
+ def cascade_update_to_create?
349
+ cascaded? and not @flags.include?(:no_cascade_update_to_create)
350
+ end
351
+
352
+ # A Java property attribute is saved if none of the following conditions hold:
353
+ # * the attribute :unsaved flag is set
354
+ # * the attribute is {#proxied_save?}
355
+ # and any of the following conditions hold:
356
+ # * the attibute is {#nondomain?}
357
+ # * the attribute is cascaded
358
+ # * the attribute value is not a collection
359
+ # * the attribute does not have an inverse
360
+ # * the attribute :saved flag is set
361
+ #
362
+ # @return [Boolean] whether this attribute is saved in a create or update operation
363
+ def saved?
364
+ @flags.include?(:saved) or
365
+ (java_property? and not @flags.include?(:unsaved) and not proxied_save? and
366
+ (nondomain? or cascaded? or not collection? or inverse.nil? or unidirectional_java_dependent?))
367
+ end
368
+
369
+ # @return [Boolean] whether this attribute is not {#saved?}
370
+ def unsaved?
371
+ not saved?
372
+ end
373
+
374
+ # @return [Boolean] whether the attribute return {#type} is a Resource class which
375
+ # implements the saver_proxy method
376
+ def proxied_save?
377
+ domain? and type.method_defined?(:saver_proxy)
378
+ end
379
+
380
+ # Returns whether this attribute's referents must exist before an instance of the
381
+ # declarer class can be created. An attribute is a storable prerequisite if it is
382
+ # either:
383
+ # * a {#cascaded?} dependent which does not #{#cascade_update_to_create?}, or
384
+ # * a {#saved?} {#independent?} 1:M or M:N association.
385
+ #
386
+ # @return [Boolean] whether this attribute is a create prerequisite
387
+ def storable_prerequisite?
388
+ return true if cascaded? and @flags.include?(:no_cascade_update_to_create)
389
+ return false unless independent? and saved?
390
+ return true unless collection?
391
+ inv_md = inverse_metadata
392
+ inv_md.nil? or inv_md.collection?
393
+ end
394
+
395
+ # @return [Boolean] whether this attribute is a collection with a collection inverse
396
+ def many_to_many?
397
+ return false unless collection?
398
+ inv_md = inverse_metadata
399
+ inv_md and inv_md.collection?
400
+ end
401
+
402
+ # @return [Boolean] whether the subject attribute is not saved
403
+ def transient?
404
+ not saved?
405
+ end
406
+
407
+ # Returns whether this attribute is set on the server as a side-effect
408
+ # of a change to the declarer object. The volatile attributes include
409
+ # those which are {#unsaved?} and those which are saved but marked
410
+ # with the +:volatile+ flag.
411
+ #
412
+ # @return [Boolean] whether this attribute's value is determined by the server
413
+ def volatile?
414
+ unsaved? or @flags.include?(:volatile)
415
+ end
416
+
417
+ # @return [Boolean] whether this is a non-collection Java attribute
418
+ def searchable?
419
+ java_property? and not collection?
420
+ end
421
+
422
+ # @return [Boolean] whether the subject attribute is a dependency owner
423
+ def owner?
424
+ @flags.include?(:owner)
425
+ end
426
+
427
+ # @return [Boolean] whether this is a dependent attribute which has exactly one owner value chosen from
428
+ # several owner attributes.
429
+ def disjoint?
430
+ @flags.include?(:disjoint)
431
+ end
432
+
433
+ # @return [Boolean] whether this attribute is a dependent which does not have a Java inverse owner attribute
434
+ def unidirectional_java_dependent?
435
+ dependent? and java_property? and not bidirectional_java_association?
436
+ end
437
+
438
+ # @return [Boolean] whether this is a Java attribute which has a Java inverse
439
+ def bidirectional_java_association?
440
+ inverse and java_property? and inverse_metadata.java_property?
441
+ end
442
+
443
+ def to_sym
444
+ @symbol
445
+ end
446
+
447
+ def to_s
448
+ @symbol.to_s
449
+ end
450
+
451
+ alias :inspect :to_s
452
+
453
+ alias :qp :to_s
454
+
455
+ protected
456
+
457
+ # Duplicates the mutable content as part of a {#deep_copy}.
458
+ def dup_content
459
+ # keep the copied flags but don't share them
460
+ @flags = @flags.dup
461
+ # restrictions are neither shared nor copied
462
+ @restrictions = nil
463
+ end
464
+
465
+ # Restricts this attribute's inverse to an attribute declared by this
466
+ # attribute's type. For example, if:
467
+ # * +AbstractProtocol.coordinator+ has inverse +Administrator.protocol+
468
+ # * +AbstractProtocol+ has subclass +StudyProtocol+
469
+ # * +StudyProtocol.coordinator+ returns a +StudyCoordinator+
470
+ # * +AbstractProtocol.coordinator+ is restricted to +StudyProtocol+
471
+ # then calling this method on the +StudyProtocol.coordinator+ restriction
472
+ # sets the +StudyProtocol.coordinator+ inverse to +StudyCoordinator.coordinator+.
473
+ #
474
+ # @param [Attribute, nil] inv_md the inverse attribute to restrict
475
+ # (default is the current inverse)
476
+ def restrict_inverse_type(inv_md=nil)
477
+ # default inverse is the current inverse
478
+ inv_md ||= @inv_md || return
479
+ # the current inverse
480
+ attr = inv_md.to_sym
481
+ # If the restricted type delegates to the current inverse metadata,
482
+ # then no change is needed.
483
+ return if @type.attribute_metadata(attr) == inv_md
484
+ # clear the current inverse
485
+ @inv_md = nil
486
+ # set the inverse to the restricted attribute
487
+ self.inverse = attr
488
+ end
489
+
490
+ # @param [Attribute] other the other attribute to check
491
+ # @return [Boolean] whether the other attribute restricts this attribute
492
+ def restriction?(other)
493
+ @restrictions.include?(other)
494
+ end
495
+ private
496
+
497
+ # Creates a copy of this metadata which does not share mutable content.
498
+ def deep_copy
499
+ other = dup
500
+ other.dup_content
501
+ other
502
+ end
503
+
504
+ # Creates a new declarer attribute which restricts this attribute type or flags.
505
+ #
506
+ # @param [Class] klass the declarer class for which the restriction holds
507
+ # @return [Attribute] the metadata for the new declarer attribute
508
+ def restrict(klass)
509
+ unless klass < @declarer then
510
+ raise ArgumentError.new("Cannot restrict #{@declarer.qp}.#{self} to incompatible declarer type #{klass.qp}")
511
+ end
512
+ rst = deep_copy
513
+ # specialize the copy declarer and type
514
+ rst.declarer = klass
515
+ # Capture the restriction to propagate modifications to this metadata, esp.
516
+ # adding an inverse.
517
+ @restrictions ||= []
518
+ @restrictions << rst
519
+ rst
520
+ end
521
+
522
+ def clear_inverse
523
+ return unless @inv_md
524
+ logger.debug { "Clearing #{@declarer.qp}.#{self} inverse #{type.qp}.#{inverse}..." }
525
+ inv_inv_md = @inv_md.inverse_metadata
526
+ @inv_md = nil
527
+ if inv_inv_md then inv_inv_md.inverse = nil end
528
+ logger.debug { "Cleared #{@declarer.qp}.#{self} inverse." }
529
+ end
530
+
531
+ # @param [Symbol] the flag to set
532
+ # @raise [ArgumentError] if flag is not supported
533
+ def set_flag(flag)
534
+ return if @flags.include?(flag)
535
+ raise ArgumentError.new("Attribute flag not supported: #{flag}") unless SUPPORTED_FLAGS.include?(flag)
536
+ @flags << flag
537
+ case flag
538
+ when :owner then owner_flag_set
539
+ when :dependent then dependent_flag_set
540
+ end
541
+ end
542
+
543
+ # This method is called when the owner flag is set.
544
+ # The inverse is inferred as the referenced owner type's dependent attribute which references
545
+ # this attribute's type.
546
+ #
547
+ # @raise [MetadataError] if this attribute is dependent or an inverse could not be inferred
548
+ def owner_flag_set
549
+ if dependent? then
550
+ raise MetadataError.new("#{declarer.qp}.#{self} cannot be set as a #{type.qp} owner since it is already defined as a #{type.qp} dependent")
551
+ end
552
+ inv_attr = type.dependent_attribute(@declarer)
553
+ if inv_attr.nil? then
554
+ raise MetadataError.new("#{@declarer.qp} owner attribute #{self} does not have a #{type.qp} dependent inverse")
555
+ end
556
+ self.inverse = type.dependent_attribute(@declarer)
557
+ if inverse_metadata.logical? then @flags << :logical end
558
+ end
559
+
560
+ # Validates that this is not an owner attribute.
561
+ #
562
+ # @raise [MetadataError] if this is an owner attribute
563
+ def dependent_flag_set
564
+ if owner? then
565
+ raise MetadataError.new("#{declarer.qp}.#{self} cannot be set as a #{type.qp} dependent since it is already defined as a #{type.qp} owner")
566
+ end
567
+ end
568
+
569
+ # @return [Boolean] whether this dependent attribute is fetched. Only physical dependents are fetched by default.
570
+ def fetched_dependent?
571
+ not (logical? or @flags.include?(:unfetched))
572
+ end
573
+
574
+ # @return [Boolean] whether this independent attribute is fetched. Only abstract, non-derived independent
575
+ # references are fetched by default.
576
+ def fetched_independent?
577
+ type.abstract? and not (derived? or @flags.include?(:unfetched))
578
+ end
579
+ end
580
+ end
581
+ end