caruby-core 1.4.9 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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