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