caruby-core 1.4.2 → 1.4.3
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.
- data/History.txt +10 -0
- data/lib/caruby/cli/command.rb +10 -8
- data/lib/caruby/database/fetched_matcher.rb +28 -39
- data/lib/caruby/database/lazy_loader.rb +101 -0
- data/lib/caruby/database/persistable.rb +190 -167
- data/lib/caruby/database/persistence_service.rb +21 -7
- data/lib/caruby/database/persistifier.rb +185 -0
- data/lib/caruby/database/reader.rb +106 -176
- data/lib/caruby/database/saved_matcher.rb +56 -0
- data/lib/caruby/database/search_template_builder.rb +1 -1
- data/lib/caruby/database/sql_executor.rb +8 -7
- data/lib/caruby/database/store_template_builder.rb +134 -61
- data/lib/caruby/database/writer.rb +252 -52
- data/lib/caruby/database.rb +88 -67
- data/lib/caruby/domain/attribute_initializer.rb +16 -0
- data/lib/caruby/domain/attribute_metadata.rb +161 -72
- data/lib/caruby/domain/id_alias.rb +22 -0
- data/lib/caruby/domain/inversible.rb +91 -0
- data/lib/caruby/domain/merge.rb +116 -35
- data/lib/caruby/domain/properties.rb +1 -1
- data/lib/caruby/domain/reference_visitor.rb +207 -71
- data/lib/caruby/domain/resource_attributes.rb +93 -80
- data/lib/caruby/domain/resource_dependency.rb +22 -97
- data/lib/caruby/domain/resource_introspection.rb +21 -28
- data/lib/caruby/domain/resource_inverse.rb +134 -0
- data/lib/caruby/domain/resource_metadata.rb +41 -19
- data/lib/caruby/domain/resource_module.rb +42 -33
- data/lib/caruby/import/java.rb +8 -9
- data/lib/caruby/migration/migrator.rb +20 -7
- data/lib/caruby/migration/resource_module.rb +0 -2
- data/lib/caruby/resource.rb +132 -351
- data/lib/caruby/util/cache.rb +4 -1
- data/lib/caruby/util/class.rb +48 -1
- data/lib/caruby/util/collection.rb +54 -18
- data/lib/caruby/util/inflector.rb +7 -0
- data/lib/caruby/util/options.rb +35 -31
- data/lib/caruby/util/partial_order.rb +1 -1
- data/lib/caruby/util/properties.rb +2 -2
- data/lib/caruby/util/stopwatch.rb +16 -8
- data/lib/caruby/util/transitive_closure.rb +1 -1
- data/lib/caruby/util/visitor.rb +342 -328
- data/lib/caruby/version.rb +1 -1
- data/lib/caruby/yard/resource_metadata_handler.rb +8 -0
- data/lib/caruby.rb +2 -0
- metadata +10 -9
- data/lib/caruby/database/saved_merger.rb +0 -131
- data/lib/caruby/domain/annotatable.rb +0 -25
- data/lib/caruby/domain/annotation.rb +0 -23
- data/lib/caruby/import/annotatable_class.rb +0 -28
- data/lib/caruby/import/annotation_class.rb +0 -27
- data/lib/caruby/import/annotation_module.rb +0 -67
- data/lib/caruby/migration/resource.rb +0 -8
data/lib/caruby/database.rb
CHANGED
@@ -6,6 +6,7 @@ require 'caruby/util/options'
|
|
6
6
|
require 'caruby/util/visitor'
|
7
7
|
require 'caruby/util/inflector'
|
8
8
|
require 'caruby/database/persistable'
|
9
|
+
require 'caruby/database/persistifier'
|
9
10
|
require 'caruby/database/reader'
|
10
11
|
require 'caruby/database/writer'
|
11
12
|
require 'caruby/database/persistence_service'
|
@@ -42,9 +43,38 @@ module CaRuby
|
|
42
43
|
# store method. CaRuby::Resource sets reasonable default values, recognizes application dependencies and steers
|
43
44
|
# around caBIG idiosyncracies to the extent possible.
|
44
45
|
class Database
|
45
|
-
include Reader, Writer, Validation
|
46
|
+
include Reader, Writer, Persistifier, Validation
|
47
|
+
|
48
|
+
# Database CRUD operation.
|
49
|
+
class Operation
|
50
|
+
attr_reader :type, :subject, :attribute
|
51
|
+
|
52
|
+
# @param [:find, :query, :create, :udate, :delete] type the database operation type
|
53
|
+
# @param [Persistable] subject the domain object on which the operation is performed
|
54
|
+
# @param [{Symbol => Object}, Symbol, nil] the operation characteristics
|
55
|
+
# @option opts [Symbol] :attribute the query attribute
|
56
|
+
# @option opts [Boolean] :autogenerated whether this is an auto-generated subject update
|
57
|
+
def initialize(type, subject, opts=nil)
|
58
|
+
@type = type
|
59
|
+
@subject = subject
|
60
|
+
@attribute = Options.get(:attribute, opts)
|
61
|
+
@autogenerated = Options.get(:autogenerated, opts, false)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Boolean] whether this operation is an update of an auto-generated subject
|
65
|
+
def autogenerated?
|
66
|
+
@autogenerated
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
"#{@subject.qp} #{attribute} #{type}"
|
71
|
+
end
|
72
|
+
end
|
46
73
|
|
47
74
|
attr_reader :operations
|
75
|
+
|
76
|
+
# @return [PersistenceService] the services used by this database
|
77
|
+
attr_reader :persistence_services
|
48
78
|
|
49
79
|
# Creates a new Database with the specified service name and options.
|
50
80
|
#
|
@@ -64,6 +94,7 @@ module CaRuby
|
|
64
94
|
@host = Options.get(:host, opts)
|
65
95
|
# class => service hash; default is the catissuecore app service
|
66
96
|
@def_persist_svc = PersistenceService.new(service_name, :host => @host)
|
97
|
+
@persistence_services = [@def_persist_svc].to_set
|
67
98
|
@cls_svc_hash = Hash.new(@def_persist_svc)
|
68
99
|
# the create/update nested operations
|
69
100
|
@operations = []
|
@@ -82,6 +113,11 @@ module CaRuby
|
|
82
113
|
# call the block and close when done
|
83
114
|
yield(self) ensure close
|
84
115
|
end
|
116
|
+
|
117
|
+
# Clears the cache.
|
118
|
+
def clear
|
119
|
+
@cache.clear
|
120
|
+
end
|
85
121
|
|
86
122
|
# Releases database resources. This method should be called when database interaction
|
87
123
|
# is completed.
|
@@ -93,46 +129,49 @@ module CaRuby
|
|
93
129
|
logger.error("Session termination unsuccessful - #{e.message}")
|
94
130
|
end
|
95
131
|
# clear the cache
|
96
|
-
|
132
|
+
clear
|
97
133
|
logger.info("Disconnected from application server.")
|
98
134
|
@session = nil
|
99
135
|
end
|
100
136
|
|
101
|
-
|
102
|
-
|
137
|
+
# @return [Numeric] the execution time in seconds spent since the last open
|
138
|
+
def execution_time
|
103
139
|
persistence_services.inject(0) do |total, svc|
|
104
140
|
st = svc.timer.elapsed
|
105
141
|
total + st
|
106
142
|
end
|
107
143
|
end
|
108
144
|
|
109
|
-
# Returns the PersistanceService to use for the given domain object
|
110
|
-
#
|
111
|
-
|
145
|
+
# Returns the PersistanceService to use for the given domain object.
|
146
|
+
# This base method always returns the standard application service.
|
147
|
+
# Subclasses can override for specialized services. A session is
|
148
|
+
# started on demand if necessary.
|
149
|
+
#
|
150
|
+
# @param [Persistable] obj the domain object
|
151
|
+
# @return [PersistanceService] the service for the domain object
|
152
|
+
def persistence_service(obj)
|
112
153
|
start_session if @session.nil?
|
113
|
-
|
114
|
-
klass = Class === obj ? obj : obj.class
|
115
|
-
@cls_svc_hash[klass]
|
116
|
-
end
|
117
|
-
|
118
|
-
# Returns all PersistanceServices used by this database.
|
119
|
-
def persistence_services
|
120
|
-
[@def_persist_svc].to_set.merge!(@cls_svc_hash.values)
|
154
|
+
@def_persist_svc
|
121
155
|
end
|
122
156
|
|
123
|
-
#
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
svc.timer.reset
|
129
|
-
total + st
|
130
|
-
end
|
157
|
+
# Adds the given service to this database.
|
158
|
+
#
|
159
|
+
# @param [PersistenceService] service the service to add
|
160
|
+
def add_persistence_service(service)
|
161
|
+
@persistence_services << service
|
131
162
|
end
|
163
|
+
|
164
|
+
alias :to_s :print_class_and_id
|
165
|
+
|
166
|
+
alias :inspect :to_s
|
167
|
+
|
168
|
+
## Utility classes and methods, used by Query and Store mix-ins ##
|
169
|
+
|
170
|
+
private
|
132
171
|
|
133
172
|
# A mergeable autogenerated operation is recursively defined as:
|
134
|
-
# * a create
|
135
|
-
# * an update in the context of a mergeable autogenerated operation
|
173
|
+
# * a create of an object with auto-generated dependents
|
174
|
+
# * an update of an auto-generated dependent in the context of a mergeable autogenerated operation
|
136
175
|
#
|
137
176
|
# @return whether the innermost operation conforms to the above criterion
|
138
177
|
def mergeable_autogenerated_operation?
|
@@ -145,7 +184,7 @@ module CaRuby
|
|
145
184
|
end
|
146
185
|
if op.type == :create then
|
147
186
|
# innermost or owner create
|
148
|
-
return
|
187
|
+
return (not op.subject.class.autogenerated_dependent_attributes.empty?)
|
149
188
|
elsif op.type != :update then
|
150
189
|
# not a save
|
151
190
|
return false
|
@@ -155,65 +194,47 @@ module CaRuby
|
|
155
194
|
end
|
156
195
|
false
|
157
196
|
end
|
158
|
-
|
159
|
-
alias :to_s :print_class_and_id
|
160
|
-
|
161
|
-
alias :inspect :to_s
|
162
|
-
|
163
|
-
private
|
164
|
-
|
165
|
-
## Utility classes and methods, used by Query and Store mix-ins ##
|
166
|
-
|
167
|
-
# Database CRUD operation.
|
168
|
-
class Operation
|
169
|
-
attr_reader :type, :subject, :attribute
|
170
|
-
|
171
|
-
def initialize(type, subject, attribute=nil)
|
172
|
-
@type = type
|
173
|
-
@subject = subject
|
174
|
-
@attribute = attribute
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
197
|
+
|
178
198
|
# Performs the operation given by the given op symbol on obj by calling the block given to this method.
|
179
|
-
#
|
180
|
-
#
|
181
|
-
|
199
|
+
# Lazy loading is suspended during the operation.
|
200
|
+
#
|
201
|
+
# @param [:find, :query, :create, :udate, :delete] op the database operation type
|
202
|
+
# @param [Resource] obj the domain object on which the operation is performed
|
203
|
+
# @param opts (#see Operation#initialize)
|
204
|
+
# @yield the database operation block
|
205
|
+
# @return the result of calling the operation block
|
206
|
+
def perform(op, obj, opts=nil)
|
182
207
|
op_s = op.to_s.capitalize_first
|
183
|
-
|
208
|
+
attr = Options.get(:attribute, opts)
|
209
|
+
attr_s = " #{attr}" if attr
|
210
|
+
ag_s = " autogenerated" if Options.get(:autogenerated, opts)
|
184
211
|
ctxt_s = " in context #{print_operations}" unless @operations.empty?
|
185
|
-
logger.info(">> #{op_s} #{obj.pp_s(:single_line)}#{attr_s}#{ctxt_s}...")
|
186
|
-
@operations.push(Operation.new(op, obj,
|
212
|
+
logger.info(">> #{op_s}#{ag_s} #{obj.pp_s(:single_line)}#{attr_s}#{ctxt_s}...")
|
213
|
+
@operations.push(Operation.new(op, obj, opts))
|
187
214
|
begin
|
188
215
|
# perform the operation
|
189
|
-
result = yield
|
216
|
+
result = @lazy_loader.suspend { yield }
|
190
217
|
ensure
|
191
218
|
# the operation is done
|
192
219
|
@operations.pop
|
193
|
-
# If this is a top-level operation, then clear the
|
194
|
-
if @operations.empty? then
|
195
|
-
@cache.clear
|
196
|
-
@transients.clear
|
197
|
-
end
|
220
|
+
# If this is a top-level operation, then clear the transient set.
|
221
|
+
if @operations.empty? then @transients.clear end
|
198
222
|
end
|
199
223
|
logger.info("<< Completed #{obj.qp}#{attr_s} #{op}.")
|
200
224
|
result
|
201
225
|
end
|
202
|
-
|
226
|
+
|
227
|
+
def each_persistence_service(&block)
|
228
|
+
ObjectSpace.each_object(PersistenceService, &block)
|
229
|
+
end
|
230
|
+
|
203
231
|
# @return [Cache] a new object cache.
|
204
232
|
def create_cache
|
205
233
|
# JRuby alert - identifier is not a stable object when fetched from the database, i.e.:
|
206
234
|
# obj.identifier.equal?(obj.identifier) #=> false
|
207
235
|
# This is probably an artifact of jRuby Numeric - Java Long conversion interaction
|
208
236
|
# combined with hash access use of the eql? method. Work-around is to make a Ruby Integer.
|
209
|
-
|
210
|
-
copier = Proc.new do |src|
|
211
|
-
copy = src.copy
|
212
|
-
logger.debug { "Fetched #{src.qp} copied to #{copy.qp}." }
|
213
|
-
copy
|
214
|
-
end
|
215
|
-
# the fetched object cache
|
216
|
-
Cache.new(copier) do |obj|
|
237
|
+
Cache.new do |obj|
|
217
238
|
raise ArgumentError.new("Can't cache object without identifier: #{obj}") unless obj.identifier
|
218
239
|
obj.identifier.to_s.to_i
|
219
240
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CaRuby
|
2
|
+
module AttributeInitializer
|
3
|
+
# Initializes a new instance of this Resource class with optional attribute => value hash
|
4
|
+
# @param [{Symbol => Object}] the optional attribute => value hash
|
5
|
+
# @return
|
6
|
+
def initialize(hash=nil)
|
7
|
+
super()
|
8
|
+
if hash then
|
9
|
+
unless Hashable === hash then
|
10
|
+
raise ArgumentError.new("#{qp} initializer argument type not supported: #{hash.class.qp}")
|
11
|
+
end
|
12
|
+
merge_attributes(hash)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -14,23 +14,22 @@ module CaRuby
|
|
14
14
|
# The supported attribute qualifier flags.
|
15
15
|
SUPPORTED_FLAGS = [
|
16
16
|
:autogenerated, :collection, :dependent, :derived, :logical, :disjoint, :owner, :cascaded,
|
17
|
-
:no_cascade_update_to_create, :saved, :unsaved, :
|
17
|
+
:no_cascade_update_to_create, :saved, :unsaved, :optional, :fetched, :unfetched,
|
18
18
|
:create_only, :update_only, :unidirectional, :volatile].to_set
|
19
19
|
|
20
|
-
#
|
20
|
+
# @return [(Symbol, Symbol)] the standard attribute reader and writer methods
|
21
21
|
attr_reader :accessors
|
22
22
|
|
23
|
-
#
|
23
|
+
# @return [Class] the declaring class
|
24
24
|
attr_accessor :declarer
|
25
|
+
protected :declarer=
|
25
26
|
|
26
|
-
#
|
27
|
-
|
27
|
+
# @return [Class] the return type
|
28
|
+
attr_reader :type
|
28
29
|
|
29
|
-
#
|
30
|
+
# @return [<Symbol>] the qualifier flags
|
30
31
|
# @see SUPPORTED_FLAGS
|
31
32
|
attr_accessor :flags
|
32
|
-
|
33
|
-
protected :declarer=
|
34
33
|
|
35
34
|
# Creates a new AttributeMetadata from the given attribute.
|
36
35
|
#
|
@@ -82,33 +81,41 @@ module CaRuby
|
|
82
81
|
def unidirectional?
|
83
82
|
inverse.nil?
|
84
83
|
end
|
84
|
+
|
85
|
+
# @param [Class] the attribute return type
|
86
|
+
def type=(klass)
|
87
|
+
return if klass == @type
|
88
|
+
@type = klass
|
89
|
+
if @inv_md then
|
90
|
+
self.inverse = @inv_md.to_sym
|
91
|
+
logger.debug { "Reset #{@declarer.qp}.#{self} inverse from #{@inv_md.type}.#{@inv_md} to #{klass}#{inv_md}." }
|
92
|
+
end
|
93
|
+
end
|
85
94
|
|
86
95
|
# Creates a new declarer attribute which restricts this attribute {#type} to the given type.
|
87
96
|
#
|
88
|
-
|
89
|
-
# @
|
90
|
-
|
91
|
-
|
92
|
-
raise ArgumentError.new("Cannot restrict #{self.declarer}.#{self} to incompatible declarer type #{declarer}")
|
93
|
-
end
|
97
|
+
# @param declarer (see #restrict)
|
98
|
+
# @param [Class] type the restricted subclass of this attribute's return type
|
99
|
+
# @return (see #restrict)
|
100
|
+
def restrict_type(declarer, type)
|
94
101
|
if self.type and not type < self.type then
|
95
|
-
raise ArgumentError.new("Cannot restrict #{self.declarer}.#{self} to incompatible type #{type}")
|
102
|
+
raise ArgumentError.new("Cannot restrict #{self.declarer.qp}.#{self} to incompatible attribute type #{type.qp}")
|
96
103
|
end
|
97
|
-
|
98
|
-
rstr = @restrictions
|
99
|
-
@restrictions = nil
|
100
|
-
copy = dup
|
101
|
-
# Restore the restrictions. Initialize the array if necessary, since the copy
|
102
|
-
# will be added to the list.
|
103
|
-
@restrictions = rstr || []
|
104
|
-
# specialize the copy declarer and type
|
105
|
-
copy.declarer = declarer
|
104
|
+
copy = restrict(declarer)
|
106
105
|
copy.type = type
|
107
106
|
# specialize the inverse to the restricted type attribute, if necessary
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
107
|
+
copy.restrict_inverse_type
|
108
|
+
copy
|
109
|
+
end
|
110
|
+
|
111
|
+
# Creates a new declarer attribute which qualifies this attribute for the given declarer.
|
112
|
+
#
|
113
|
+
# @param declarer (see #restrict)
|
114
|
+
# @param [<Symbol>] flags the additional flags for the restricted attribute
|
115
|
+
# @return (see #restrict)
|
116
|
+
def restrict_flags(declarer, *flags)
|
117
|
+
copy = restrict(declarer)
|
118
|
+
copy.qualify(*flags)
|
112
119
|
copy
|
113
120
|
end
|
114
121
|
|
@@ -118,26 +125,34 @@ module CaRuby
|
|
118
125
|
#
|
119
126
|
# @param attribute the inverse attribute
|
120
127
|
def inverse=(attribute)
|
128
|
+
return if inverse == attribute
|
129
|
+
# if no attribute, then the clear the existing inverse, if any
|
130
|
+
return clear_inverse if attribute.nil?
|
131
|
+
|
121
132
|
logger.debug { "Set #{@declarer.qp}.#{self} inverse to #{type.qp}.#{attribute}." }
|
122
|
-
|
123
|
-
|
124
|
-
|
133
|
+
begin
|
134
|
+
@inv_md = type.attribute_metadata(attribute)
|
135
|
+
rescue NameError
|
136
|
+
raise MetadataError.new("#{@declarer.qp}.#{self} inverse attribute #{type.qp}.#{attribute} not found - #{$!}")
|
125
137
|
end
|
138
|
+
|
139
|
+
# the inverse of the inverse
|
126
140
|
inv_inv_md = @inv_md.inverse_attribute_metadata
|
141
|
+
# If the inverse of the inverse is already set to a different attribute, then raise an exception.
|
142
|
+
# Otherwise, it there is an inverse, then set the inverse of the inverse to this attribute.
|
127
143
|
if inv_inv_md then
|
128
144
|
unless inv_inv_md == self then
|
129
|
-
|
130
|
-
@inv_md.inverse = @symbol
|
131
|
-
else
|
132
|
-
raise MetadataError.new("Cannot set #{type.qp}.#{@inv_md} inverse attribute to #{@declarer.qp}.#{self} since it conflicts with existing inverse #{@inv_md.inverse}")
|
133
|
-
end
|
145
|
+
raise MetadataError.new("Cannot set #{type.qp}.#{@inv_md} inverse attribute to #{@declarer.qp}.#{self} since it conflicts with existing inverse #{@inv_md.inverse}")
|
134
146
|
end
|
135
147
|
else
|
148
|
+
# set the inverse of the inverse
|
136
149
|
@inv_md.inverse = @symbol
|
137
|
-
|
150
|
+
# if this attribute is disjoint, then so is the inverse.
|
151
|
+
@inv_md.qualify(:disjoint) if disjoint?
|
138
152
|
end
|
153
|
+
|
139
154
|
# propagate to restrictions
|
140
|
-
if @restrictions then @restrictions.each { |attr_md| attr_md.
|
155
|
+
if @restrictions then @restrictions.each { |attr_md| attr_md.restrict_inverse_type } end
|
141
156
|
end
|
142
157
|
|
143
158
|
# @return [AttributeMetadata, nil] the metadata for the {#inverse} attribute, if any
|
@@ -197,7 +212,7 @@ module CaRuby
|
|
197
212
|
#
|
198
213
|
# @return [Boolean] whether the attribute references a dependent
|
199
214
|
def dependent?
|
200
|
-
|
215
|
+
@flags.include?(:dependent)
|
201
216
|
end
|
202
217
|
|
203
218
|
# Returns whether the subject attribute is marked as optional in a create.
|
@@ -224,39 +239,44 @@ module CaRuby
|
|
224
239
|
def autogenerated?
|
225
240
|
@flags.include?(:autogenerated)
|
226
241
|
end
|
227
|
-
|
228
|
-
# Returns whether
|
229
|
-
#
|
230
|
-
|
231
|
-
|
242
|
+
|
243
|
+
# Returns whether this attribute must be fetched when a declarer instance is saved.
|
244
|
+
# An attribute is a saved fetch attribute if either of the following conditions hold:
|
245
|
+
# * it is {#autogenerated?}
|
246
|
+
# * it is {#cascaded?} and marked with the +:unfetched+ flag.
|
247
|
+
#
|
248
|
+
# @return [Boolean] whether the subject attribute must be refetched in order to reflect
|
249
|
+
# the database content
|
250
|
+
def saved_fetch?
|
251
|
+
autogenerated? or (cascaded? and @flags.include?(:unfetched))
|
232
252
|
end
|
233
253
|
|
234
254
|
# Returns whether the subject attribute is a dependent whose owner does not automatically
|
235
255
|
# cascade application service creation or update to the dependent. It is incumbent upon
|
236
256
|
# CaRuby::Database to cascade the changes.
|
257
|
+
#
|
258
|
+
# @return [Boolean] whether the attribute is an uncascaded dependent
|
237
259
|
def logical?
|
238
|
-
|
260
|
+
@flags.include?(:logical)
|
239
261
|
end
|
240
262
|
|
241
|
-
#
|
242
|
-
def annotation?
|
243
|
-
false
|
244
|
-
# TODO - enable when annotation enabled
|
245
|
-
end
|
246
|
-
|
247
|
-
# @return whether this attribute is derived from another attribute
|
248
|
-
# This occurs when the attribute value is set by setting another attribute, e.g. if this
|
263
|
+
# An attribute is derived if the attribute value is set by setting another attribute, e.g. if this
|
249
264
|
# attribute is the inverse of a dependent owner attribute.
|
265
|
+
#
|
266
|
+
# @return [Boolean] whether this attribute is derived from another attribute
|
250
267
|
def derived?
|
251
268
|
@flags.include?(:derived) or (dependent? and not inverse.nil?)
|
252
269
|
end
|
253
270
|
|
254
|
-
# @return this attribute's inverse attribute if the inverse is a derived attribute, or nil otherwise
|
271
|
+
# @return [Boolean] this attribute's inverse attribute if the inverse is a derived attribute, or nil otherwise
|
255
272
|
def derived_inverse
|
256
273
|
@inv_md.to_sym if @inv_md and @inv_md.derived?
|
257
274
|
end
|
258
275
|
|
259
|
-
#
|
276
|
+
# An independent attribute is a reference to one or more non-dependent Resource objects.
|
277
|
+
# An {#owner?} attribute is independent.
|
278
|
+
#
|
279
|
+
# @return [Boolean] whether the subject attribute is a non-dependent domain attribute
|
260
280
|
def independent?
|
261
281
|
domain? and not dependent?
|
262
282
|
end
|
@@ -323,8 +343,9 @@ module CaRuby
|
|
323
343
|
#
|
324
344
|
# @return [Boolean] whether this attribute is saved in a create or update operation
|
325
345
|
def saved?
|
326
|
-
|
327
|
-
(
|
346
|
+
@flags.include?(:saved) or
|
347
|
+
(java_property? and not @flags.include?(:unsaved) and not proxied_save? and
|
348
|
+
(nondomain? or cascaded? or not collection? or inverse.nil? or unidirectional_java_dependent?))
|
328
349
|
end
|
329
350
|
|
330
351
|
# @return [Boolean] whether this attribute is not {#saved?}
|
@@ -337,12 +358,6 @@ module CaRuby
|
|
337
358
|
def proxied_save?
|
338
359
|
domain? and type.method_defined?(:saver_proxy)
|
339
360
|
end
|
340
|
-
|
341
|
-
# Each saved attribute is a saved mergeable attribute unless the :saved_unmergeable flag is set.
|
342
|
-
# @return [Boolean] whether this attribute can be merged from a save result
|
343
|
-
def saved_mergeable?
|
344
|
-
not @flags.include?(:saved_unmergeable)
|
345
|
-
end
|
346
361
|
|
347
362
|
# Returns whether this attribute's referents must exist before an instance of the
|
348
363
|
# declarer class can be created. An attribute is a storable prerequisite if it is
|
@@ -396,7 +411,13 @@ module CaRuby
|
|
396
411
|
def disjoint?
|
397
412
|
@flags.include?(:disjoint)
|
398
413
|
end
|
399
|
-
|
414
|
+
|
415
|
+
# @param [Symbol] attribute the attribute to check
|
416
|
+
# @return [Boolean] whether the attribute is part of a 1:1 non-dependency association
|
417
|
+
def one_to_one_bidirectional_independent?
|
418
|
+
independent? and not owner? and not collection? and @inv_md and not @inv_md.collection?
|
419
|
+
end
|
420
|
+
|
400
421
|
# @return [Boolean] whether this attribute is a dependent which does not have a Java inverse owner attribute
|
401
422
|
def unidirectional_java_dependent?
|
402
423
|
# TODO - can this be relaxed to java_unidirectional? i.e. eliminate dependent filter
|
@@ -420,21 +441,89 @@ module CaRuby
|
|
420
441
|
|
421
442
|
alias :qp :to_s
|
422
443
|
|
444
|
+
protected
|
445
|
+
|
446
|
+
# Duplicates the mutable content as part of a {#deep_copy}.
|
447
|
+
def dup_content
|
448
|
+
# keep the copied flags but don't share them
|
449
|
+
@flags = @flags.dup
|
450
|
+
# restrictions are neither shared nor copied
|
451
|
+
@restrictions = nil
|
452
|
+
end
|
453
|
+
|
454
|
+
# If there is a current inverse which can be restricted to an attribute in the scope of
|
455
|
+
# this metadata's restricted type, then reset the inverse to that attribute.
|
456
|
+
def restrict_inverse_type
|
457
|
+
return unless @inv_md
|
458
|
+
# the current inverse
|
459
|
+
attr = inverse
|
460
|
+
# the restricted type's metadata for the current inverse
|
461
|
+
rstr_inv_md = @type.attribute_metadata(attr)
|
462
|
+
# bail if the restricted type delegates to the current inverse metadata
|
463
|
+
return if rstr_inv_md == @inv_md
|
464
|
+
# clear the inverse
|
465
|
+
@inv_md = nil
|
466
|
+
# reset the inverse to the restricted attribute
|
467
|
+
self.inverse = attr
|
468
|
+
end
|
469
|
+
|
423
470
|
private
|
424
|
-
|
471
|
+
|
472
|
+
# Creates a copy of this metadata which does not share mutable content.
|
473
|
+
def deep_copy
|
474
|
+
copy = dup
|
475
|
+
copy.dup_content
|
476
|
+
copy
|
477
|
+
end
|
478
|
+
|
479
|
+
# Creates a new declarer attribute which restricts this attribute type or flags.
|
480
|
+
#
|
481
|
+
# @param [Class] declarer the class for which the restriction holds
|
482
|
+
# @return [AttributeMetadata] the metadata for the new declarer attribute
|
483
|
+
def restrict(declarer)
|
484
|
+
unless declarer < self.declarer then
|
485
|
+
raise ArgumentError.new("Cannot restrict #{self.declarer.qp}.#{self} to incompatible declarer type #{declarer.qp}")
|
486
|
+
end
|
487
|
+
copy = deep_copy
|
488
|
+
# specialize the copy declarer and type
|
489
|
+
copy.declarer = declarer
|
490
|
+
# Capture the restriction to propagate modifications to this metadata, esp.
|
491
|
+
# adding an inverse.
|
492
|
+
@restrictions ||= []
|
493
|
+
@restrictions << copy
|
494
|
+
copy
|
495
|
+
end
|
496
|
+
|
497
|
+
def clear_inverse
|
498
|
+
return unless @inv_md
|
499
|
+
logger.debug { "Clearing #{@declarer.qp}.#{self} inverse #{type.qp}.#{inverse}..." }
|
500
|
+
inv_inv_md = @inv_md.inverse_attribute_metadata
|
501
|
+
@inv_md = nil
|
502
|
+
if inv_inv_md then inv_inv_md.inverse = nil end
|
503
|
+
logger.debug { "Cleared #{@declarer.qp}.#{self} inverse." }
|
504
|
+
end
|
505
|
+
|
425
506
|
# @param [Symbol] the flag to set
|
426
507
|
# @raise [ArgumentError] if flag is not supported
|
427
508
|
def set_flag(flag)
|
428
509
|
return if @flags.include?(flag)
|
429
510
|
raise ArgumentError.new("Attribute flag not supported: #{flag}") unless SUPPORTED_FLAGS.include?(flag)
|
430
511
|
@flags << flag
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
512
|
+
case flag
|
513
|
+
when :owner then
|
514
|
+
if dependent? then
|
515
|
+
raise MetadataError.new("#{declarer.qp}.#{self} cannot be set as a #{type.qp} owner since it is already defined as a #{type.qp} dependent")
|
516
|
+
end
|
517
|
+
inv_attr = type.dependent_attribute(@declarer)
|
518
|
+
if inv_attr.nil? then
|
519
|
+
raise MetadataError.new("#{@declarer.qp} owner attribute #{self} does not have a #{type.qp} dependent inverse")
|
520
|
+
end
|
521
|
+
self.inverse = type.dependent_attribute(@declarer)
|
522
|
+
if inverse_attribute_metadata.logical? then @flags << :logical end
|
523
|
+
when :dependent then
|
524
|
+
if owner? then
|
525
|
+
raise MetadataError.new("#{declarer.qp}.#{self} cannot be set as a #{type.qp} dependent since it is already defined as a #{type.qp} owner")
|
526
|
+
end
|
438
527
|
end
|
439
528
|
end
|
440
529
|
|