caruby-core 1.5.5 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/History.md +5 -1
- data/lib/caruby.rb +3 -5
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/database.rb +53 -69
- data/lib/caruby/database/application_service.rb +25 -0
- data/lib/caruby/database/cache.rb +60 -0
- data/lib/caruby/database/fetched_matcher.rb +52 -38
- data/lib/caruby/database/lazy_loader.rb +4 -4
- data/lib/caruby/database/operation.rb +34 -0
- data/lib/caruby/database/persistable.rb +171 -86
- data/lib/caruby/database/persistence_service.rb +32 -34
- data/lib/caruby/database/persistifier.rb +100 -43
- data/lib/caruby/database/reader.rb +107 -85
- data/lib/caruby/database/reader_template_builder.rb +60 -0
- data/lib/caruby/database/saved_matcher.rb +3 -3
- data/lib/caruby/database/sql_executor.rb +88 -17
- data/lib/caruby/database/writer.rb +213 -177
- data/lib/caruby/database/writer_template_builder.rb +334 -0
- data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
- data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
- data/lib/caruby/{util → helpers}/person.rb +3 -3
- data/lib/caruby/{util → helpers}/properties.rb +7 -9
- data/lib/caruby/{util → helpers}/roman.rb +2 -2
- data/lib/caruby/{util → helpers}/version.rb +1 -1
- data/lib/caruby/json/deserializer.rb +2 -2
- data/lib/caruby/json/serializer.rb +49 -7
- data/lib/caruby/metadata.rb +30 -0
- data/lib/caruby/metadata/java_property.rb +21 -0
- data/lib/caruby/metadata/propertied.rb +191 -0
- data/lib/caruby/metadata/property.rb +22 -0
- data/lib/caruby/metadata/property_characteristics.rb +201 -0
- data/lib/caruby/migration/migratable.rb +11 -182
- data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
- data/lib/caruby/resource.rb +20 -823
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/cache_test.rb +54 -0
- data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
- data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
- data/test/lib/caruby/helpers/properties_test.rb +34 -0
- data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
- data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
- data/test/lib/helper.rb +7 -0
- metadata +161 -214
- data/lib/caruby/cli/application.rb +0 -36
- data/lib/caruby/cli/command.rb +0 -202
- data/lib/caruby/csv/csv_mapper.rb +0 -159
- data/lib/caruby/csv/csvio.rb +0 -203
- data/lib/caruby/database/search_template_builder.rb +0 -56
- data/lib/caruby/database/store_template_builder.rb +0 -278
- data/lib/caruby/domain.rb +0 -193
- data/lib/caruby/domain/attribute.rb +0 -584
- data/lib/caruby/domain/attributes.rb +0 -628
- data/lib/caruby/domain/dependency.rb +0 -225
- data/lib/caruby/domain/id_alias.rb +0 -22
- data/lib/caruby/domain/importer.rb +0 -183
- data/lib/caruby/domain/introspection.rb +0 -176
- data/lib/caruby/domain/inverse.rb +0 -172
- data/lib/caruby/domain/inversible.rb +0 -90
- data/lib/caruby/domain/java_attribute.rb +0 -173
- data/lib/caruby/domain/merge.rb +0 -185
- data/lib/caruby/domain/metadata.rb +0 -142
- data/lib/caruby/domain/mixin.rb +0 -35
- data/lib/caruby/domain/properties.rb +0 -95
- data/lib/caruby/domain/reference_visitor.rb +0 -428
- data/lib/caruby/domain/uniquify.rb +0 -50
- data/lib/caruby/import/java.rb +0 -387
- data/lib/caruby/migration/migrator.rb +0 -918
- data/lib/caruby/migration/resource_module.rb +0 -9
- data/lib/caruby/migration/uniquify.rb +0 -17
- data/lib/caruby/util/attribute_path.rb +0 -44
- data/lib/caruby/util/cache.rb +0 -56
- data/lib/caruby/util/class.rb +0 -149
- data/lib/caruby/util/collection.rb +0 -1152
- data/lib/caruby/util/domain_extent.rb +0 -46
- data/lib/caruby/util/file_separator.rb +0 -65
- data/lib/caruby/util/inflector.rb +0 -27
- data/lib/caruby/util/log.rb +0 -95
- data/lib/caruby/util/math.rb +0 -12
- data/lib/caruby/util/merge.rb +0 -59
- data/lib/caruby/util/module.rb +0 -18
- data/lib/caruby/util/options.rb +0 -97
- data/lib/caruby/util/partial_order.rb +0 -35
- data/lib/caruby/util/pretty_print.rb +0 -204
- data/lib/caruby/util/stopwatch.rb +0 -74
- data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
- data/lib/caruby/util/transitive_closure.rb +0 -55
- data/lib/caruby/util/tree.rb +0 -48
- data/lib/caruby/util/trie.rb +0 -37
- data/lib/caruby/util/uniquifier.rb +0 -30
- data/lib/caruby/util/validation.rb +0 -20
- data/lib/caruby/util/visitor.rb +0 -365
- data/lib/caruby/util/weak_hash.rb +0 -36
- data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
- data/test/lib/caruby/csv/csvio_test.rb +0 -69
- data/test/lib/caruby/database/persistable_test.rb +0 -92
- data/test/lib/caruby/domain/domain_test.rb +0 -112
- data/test/lib/caruby/domain/inversible_test.rb +0 -99
- data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
- data/test/lib/caruby/import/java_test.rb +0 -80
- data/test/lib/caruby/import/mixed_case_test.rb +0 -14
- data/test/lib/caruby/migration/test_case.rb +0 -102
- data/test/lib/caruby/test_case.rb +0 -230
- data/test/lib/caruby/util/cache_test.rb +0 -23
- data/test/lib/caruby/util/class_test.rb +0 -61
- data/test/lib/caruby/util/collection_test.rb +0 -398
- data/test/lib/caruby/util/command_test.rb +0 -55
- data/test/lib/caruby/util/domain_extent_test.rb +0 -60
- data/test/lib/caruby/util/file_separator_test.rb +0 -30
- data/test/lib/caruby/util/inflector_test.rb +0 -12
- data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
- data/test/lib/caruby/util/merge_test.rb +0 -83
- data/test/lib/caruby/util/module_test.rb +0 -25
- data/test/lib/caruby/util/options_test.rb +0 -59
- data/test/lib/caruby/util/partial_order_test.rb +0 -42
- data/test/lib/caruby/util/pretty_print_test.rb +0 -85
- data/test/lib/caruby/util/properties_test.rb +0 -50
- data/test/lib/caruby/util/stopwatch_test.rb +0 -18
- data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
- data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
- data/test/lib/caruby/util/tree_test.rb +0 -23
- data/test/lib/caruby/util/trie_test.rb +0 -14
- data/test/lib/caruby/util/visitor_test.rb +0 -278
- data/test/lib/caruby/util/weak_hash_test.rb +0 -45
- data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
- data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
data/Gemfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
source :rubygems
|
2
|
+
gemspec
|
3
|
+
|
4
|
+
group :development do
|
5
|
+
#gem 'jinx', :path => File.dirname(__FILE__) + '/../../jinx/core'
|
6
|
+
#gem 'jinx-json', :path => File.dirname(__FILE__) + '/../../jinx/json'
|
7
|
+
#gem 'jinx-migrate', :path => File.dirname(__FILE__) + '/../../jinx/migrate'
|
8
|
+
gem 'caruby-core', :path => File.dirname(__FILE__), :require => 'caruby'
|
9
|
+
end
|
data/History.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
This history lists major release themes. See the GitHub Commits (https://github.com/caruby/core)
|
2
2
|
for change details.
|
3
3
|
|
4
|
-
1.
|
4
|
+
2.1.1 / 2011-04-13
|
5
|
+
------------------
|
6
|
+
* Simpler, more flexible meta-data loading.
|
7
|
+
|
8
|
+
1.5.5 / 2011-09-22
|
5
9
|
------------------
|
6
10
|
* Support mixed-case custom DE package name.
|
7
11
|
|
data/lib/caruby.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
# See the [http://http://caruby.rubyforge.org/](caRuby) home page for more information.
|
5
|
-
module CaRuby;end
|
1
|
+
require 'jinx'
|
2
|
+
require 'jinx/helpers/log'
|
3
|
+
require 'jinx/helpers/error'
|
Binary file
|
data/lib/caruby/database.rb
CHANGED
@@ -1,20 +1,17 @@
|
|
1
1
|
require 'generator'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require '
|
7
|
-
require 'caruby/util/inflector'
|
2
|
+
require 'jinx/helpers/collection'
|
3
|
+
require 'jinx/helpers/validation'
|
4
|
+
require 'jinx/helpers/options'
|
5
|
+
require 'jinx/helpers/visitor'
|
6
|
+
require 'jinx/helpers/inflector'
|
8
7
|
require 'caruby/database/persistable'
|
9
8
|
require 'caruby/database/persistence_service'
|
9
|
+
require 'caruby/database/operation'
|
10
10
|
require 'caruby/database/reader'
|
11
11
|
require 'caruby/database/writer'
|
12
12
|
require 'caruby/database/persistifier'
|
13
13
|
|
14
14
|
module CaRuby
|
15
|
-
# The caBIG client session class.
|
16
|
-
java_import Java::gov.nih.nci.system.comm.client.ClientSession
|
17
|
-
|
18
15
|
# Database operation error.
|
19
16
|
class DatabaseError < RuntimeError; end
|
20
17
|
|
@@ -39,36 +36,27 @@ module CaRuby
|
|
39
36
|
# The {Writer#save} method creates or updates references as necessary to persist its argument domain object.
|
40
37
|
# It is not necessary to fetch references first or follow dependency ordering rules, which can be
|
41
38
|
# implicit and tortuous in caBIG applications. Build the object you want to persist and call the
|
42
|
-
# store method. {Resource} sets reasonable default values, recognizes application dependencies and steers
|
39
|
+
# store method. {Jinx::Resource} sets reasonable default values, recognizes application dependencies and steers
|
43
40
|
# around caBIG idiosyncracies to the extent possible.
|
44
41
|
class Database
|
45
42
|
include Reader, Writer, Persistifier
|
46
43
|
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
# @return [Boolean] whether this operation is an update of an auto-generated subject
|
64
|
-
def autogenerated?
|
65
|
-
@autogenerated
|
66
|
-
end
|
67
|
-
|
68
|
-
def to_s
|
69
|
-
"#{@subject.qp} #{attribute} #{type}"
|
70
|
-
end
|
71
|
-
end
|
44
|
+
# The application and database connection access command line options.
|
45
|
+
ACCESS_OPTS = [
|
46
|
+
[:user, "--user USER", "the application login user"],
|
47
|
+
[:password, "--password PSWD", "the application login password"],
|
48
|
+
[:host, "--host HOST", "the application host name"],
|
49
|
+
[:port, "--port PORT", "the application port number"],
|
50
|
+
[:classpath, "--classpath PATH", "the application client classpath"],
|
51
|
+
[:database_host, "--database_host HOST", "the database host name"],
|
52
|
+
[:database_type, "--database_type TYPE", "the database type (mysql or oracle)"],
|
53
|
+
[:database_driver, "--database_driver DRIVER", "the database driver string"],
|
54
|
+
[:database_driver_class, "--database_driver_class CLASS", "the database driver class name"],
|
55
|
+
[:database_port, "--database_port PORT", Integer, "the database port number"],
|
56
|
+
[:database, "--database NAME", "the database name"],
|
57
|
+
[:database_user, "--database_user USER", "the database login user"],
|
58
|
+
[:database_password, "--database_password PSWD", "the database login password"]
|
59
|
+
]
|
72
60
|
|
73
61
|
attr_reader :operations
|
74
62
|
|
@@ -95,24 +83,28 @@ module CaRuby
|
|
95
83
|
# to the start_session method and run the +PSBIN::MigrationTest+ biopsy save test case.
|
96
84
|
#
|
97
85
|
# @param [String] service_name the name of the default {PersistenceService}
|
98
|
-
# @param [{Symbol => String}] opts access options
|
86
|
+
# @param [{Symbol => String}, nil] opts the access options, or nil if specified as a block
|
99
87
|
# @option opts [String] :host application service host name
|
100
88
|
# @option opts [String] :login application service login user
|
101
89
|
# @option opts [String] :password application service login password
|
90
|
+
# @yield the access options defined by a block rather than a parameter
|
102
91
|
# @example
|
103
92
|
# Database.new(:user => 'perdita', :password => 'changeMe')
|
104
|
-
def initialize(service_name, opts)
|
93
|
+
def initialize(service_name, opts=nil)
|
105
94
|
super()
|
95
|
+
# the options can be defined in a block
|
96
|
+
opts ||= yield if block_given?
|
97
|
+
# import the Java classes on demand
|
98
|
+
Database.import_java_classes
|
106
99
|
# the fetched object cache
|
107
|
-
@cache = create_cache
|
108
100
|
@defaults = {}
|
101
|
+
if opts.nil? then Jinx.fail(ArgumentError, "Missing required database access properties") end
|
109
102
|
@user = Options.get(:user, opts)
|
110
103
|
@password = Options.get(:password, opts)
|
111
104
|
host = Options.get(:host, opts)
|
112
105
|
port = Options.get(:port, opts)
|
113
106
|
# class => service hash; default is the catissuecore app service
|
114
107
|
@def_persist_svc = PersistenceService.new(service_name, :host => host, :port => port)
|
115
|
-
@def_persist_svc.app_service
|
116
108
|
@persistence_services = [@def_persist_svc].to_set
|
117
109
|
@cls_svc_hash = Hash.new(@def_persist_svc)
|
118
110
|
# the create/update nested operations
|
@@ -132,11 +124,6 @@ module CaRuby
|
|
132
124
|
# call the block and close when done
|
133
125
|
yield(self) ensure close
|
134
126
|
end
|
135
|
-
|
136
|
-
# Clears the cache.
|
137
|
-
def clear
|
138
|
-
@cache.clear
|
139
|
-
end
|
140
127
|
|
141
128
|
# Releases database resources. This method should be called when database interaction
|
142
129
|
# is completed.
|
@@ -161,15 +148,17 @@ module CaRuby
|
|
161
148
|
end
|
162
149
|
end
|
163
150
|
|
164
|
-
# Returns the PersistanceService to use for the given
|
151
|
+
# Returns the PersistanceService to use for the given {Jinx::Resource} class.
|
165
152
|
# This base method always returns the standard application service.
|
166
|
-
# Subclasses can override for specialized services. A session is
|
167
|
-
#
|
153
|
+
# Subclasses can override for specialized services. A session is started
|
154
|
+
# on demand if necessary.
|
168
155
|
#
|
169
|
-
# @param [
|
170
|
-
# @return [PersistanceService] the service
|
156
|
+
# @param [Class] klass the domain object class
|
157
|
+
# @return [PersistanceService] the corresponding service
|
171
158
|
def persistence_service(klass)
|
172
|
-
unless Class === klass then
|
159
|
+
unless Class === klass then
|
160
|
+
Jinx.fail(ArgumentError, "#{self} persistence_service argument is not a Class: {#klass.qp}")
|
161
|
+
end
|
173
162
|
start_session if @session.nil?
|
174
163
|
@def_persist_svc
|
175
164
|
end
|
@@ -188,12 +177,18 @@ module CaRuby
|
|
188
177
|
## Utility classes and methods, used by Query and Store mix-ins ##
|
189
178
|
|
190
179
|
private
|
180
|
+
|
181
|
+
# Imports this class's Java classes on demand.
|
182
|
+
def self.import_java_classes
|
183
|
+
# The caBIG client session class.
|
184
|
+
java_import Java::gov.nih.nci.system.comm.client.ClientSession unless const_defined?(:ClientSession)
|
185
|
+
end
|
191
186
|
|
192
187
|
# A mergeable autogenerated operation is recursively defined as:
|
193
188
|
# * a create of an object with auto-generated dependents
|
194
189
|
# * an update of an auto-generated dependent in the context of a mergeable autogenerated operation
|
195
190
|
#
|
196
|
-
# @return whether the innermost operation conforms to the above criterion
|
191
|
+
# @return [Boolean] whether the innermost operation conforms to the above criterion
|
197
192
|
def mergeable_autogenerated_operation?
|
198
193
|
# the inner operation subject
|
199
194
|
inner = nil
|
@@ -219,14 +214,14 @@ module CaRuby
|
|
219
214
|
# Lazy loading is suspended during the operation.
|
220
215
|
#
|
221
216
|
# @param [:find, :query, :create, :udate, :delete] op the database operation type
|
222
|
-
# @param [Resource] obj the domain object on which the operation is performed
|
217
|
+
# @param [Jinx::Resource] obj the domain object on which the operation is performed
|
223
218
|
# @param opts (#see Operation#initialize)
|
224
219
|
# @yield the database operation block
|
225
220
|
# @return the result of calling the operation block
|
226
221
|
def perform(op, obj, opts=nil)
|
227
222
|
op_s = op.to_s.capitalize_first
|
228
|
-
|
229
|
-
attr_s = " #{
|
223
|
+
pa = Options.get(:attribute, opts)
|
224
|
+
attr_s = " #{pa}" if pa
|
230
225
|
ag_s = " autogenerated" if Options.get(:autogenerated, opts)
|
231
226
|
ctxt_s = " in context #{print_operations}" unless @operations.empty?
|
232
227
|
logger.info(">> #{op_s}#{ag_s} #{obj.pp_s(:single_line)}#{attr_s}#{ctxt_s}...")
|
@@ -248,22 +243,10 @@ module CaRuby
|
|
248
243
|
ObjectSpace.each_object(PersistenceService, &block)
|
249
244
|
end
|
250
245
|
|
251
|
-
# @return [Cache] a new object cache.
|
252
|
-
def create_cache
|
253
|
-
# @quirk JRuby identifier is not a stable object when fetched from the database, i.e.:
|
254
|
-
# obj.identifier.equal?(obj.identifier) #=> false
|
255
|
-
# This is probably an artifact of jRuby Numeric - Java Long conversion interaction
|
256
|
-
# combined with hash access use of the eql? method. Work-around is to make a Ruby Integer.
|
257
|
-
Cache.new do |obj|
|
258
|
-
raise ArgumentError.new("Can't cache object without identifier: #{obj}") unless obj.identifier
|
259
|
-
obj.identifier.to_s.to_i
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
246
|
# Initializes the default application service.
|
264
247
|
def start_session
|
265
|
-
if @user.nil? then
|
266
|
-
if @password.nil? then
|
248
|
+
if @user.nil? then Jinx.fail(DatabaseError, 'Application user option missing') end
|
249
|
+
if @password.nil? then Jinx.fail(DatabaseError, 'Application password option missing') end
|
267
250
|
@session = ClientSession.instance
|
268
251
|
connect(@user, @password)
|
269
252
|
end
|
@@ -283,7 +266,8 @@ module CaRuby
|
|
283
266
|
begin
|
284
267
|
@session.start_session(user, password)
|
285
268
|
rescue Exception => e
|
286
|
-
logger.error("Login of #{user} unsuccessful - #{e.message}")
|
269
|
+
logger.error("Login of #{user} unsuccessful - #{e.message}")
|
270
|
+
raise e
|
287
271
|
end
|
288
272
|
logger.info("Connected to application server.")
|
289
273
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CaRuby
|
2
|
+
# An ApplicationService wraps a caCORE application service.
|
3
|
+
class ApplicationService
|
4
|
+
# @quirk caCORE When more than one application service is used, then the remote
|
5
|
+
# instance mustb e reinitialized every time a different application service
|
6
|
+
# is used.
|
7
|
+
#
|
8
|
+
# @param [String] the service URL
|
9
|
+
# @return the caCORE application service remote instance
|
10
|
+
def self.for(url)
|
11
|
+
# Load the Java class on demand the first time this method is called.
|
12
|
+
if @url.nil? then
|
13
|
+
java_import Java::gov.nih.nci.system.applicationservice.ApplicationServiceProvider
|
14
|
+
end
|
15
|
+
# If the url differs from the current remote instance, then reinitialize.
|
16
|
+
unless @url == url then
|
17
|
+
@url = url
|
18
|
+
logger.debug { "Connecting to service provider at #{@url}..." }
|
19
|
+
@current = ApplicationServiceProvider.remote_instance(@url)
|
20
|
+
end
|
21
|
+
@current
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'jinx/helpers/lazy_hash'
|
2
|
+
require 'jinx/helpers/key_transformer_hash'
|
3
|
+
|
4
|
+
module CaRuby
|
5
|
+
# Cache for objects held in memory and accessed by key.
|
6
|
+
class Cache
|
7
|
+
# The classes which are not cleared when {#clear} is called without the +all+ flag.
|
8
|
+
attr_reader :sticky
|
9
|
+
|
10
|
+
# @yield [item] returns the key for the given item to cache
|
11
|
+
# @yieldparam item the object to cache
|
12
|
+
def initialize
|
13
|
+
# Make the class => {key => item} hash.
|
14
|
+
# The {key => item} hash takes an item as an argument and converts
|
15
|
+
# it to the key by calling the block given to this initializer.
|
16
|
+
@ckh = Jinx::LazyHash.new do
|
17
|
+
Jinx::KeyTransformerHash.new do |obj|
|
18
|
+
yield(obj) or Jinx.fail(ArgumentError, "The object to cache does not have a key: #{obj}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
@sticky = Set.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# If there is already a cached object with the same key as the given item,
|
25
|
+
# then this method returns that cached object. Otherwise, this method caches
|
26
|
+
# the given item and returns that item.
|
27
|
+
#
|
28
|
+
# @param item the object to resolve
|
29
|
+
# @return the object cached with the same class and key as the given item
|
30
|
+
# @raise [ArgumentError] if the item does not have a key
|
31
|
+
def [](item)
|
32
|
+
@ckh[item.class][item]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds the given item to this cache, unless one already exists.
|
36
|
+
#
|
37
|
+
# @param item the object to cache
|
38
|
+
# @return the cached item
|
39
|
+
def add(item)
|
40
|
+
@ckh[item.class][item] ||= item
|
41
|
+
end
|
42
|
+
|
43
|
+
# Adds the given item to this cache. Overwrites an existing cache entry
|
44
|
+
# for the given item's key, if one already exists.
|
45
|
+
#
|
46
|
+
# @param item the object to cache
|
47
|
+
def add!(item)
|
48
|
+
@ckh[item.class][item] = item
|
49
|
+
end
|
50
|
+
|
51
|
+
# Clears the non-sticky class caches.
|
52
|
+
def clear
|
53
|
+
if @sticky.empty? then
|
54
|
+
@ckh.clear
|
55
|
+
else
|
56
|
+
@ckh.each { |klass, ch| ch.clear unless @sticky.include?(klass) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1,58 +1,72 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'jinx/helpers/options'
|
2
|
+
require 'jinx/helpers/collection'
|
3
3
|
|
4
4
|
module CaRuby
|
5
5
|
class Database
|
6
|
-
#
|
6
|
+
# Helper that matches fetched sources to targets.
|
7
7
|
class FetchedMatcher
|
8
|
-
#
|
9
|
-
|
10
|
-
|
8
|
+
# Returns a target => source match hash for the given targets and sources using
|
9
|
+
# {Jinx::Resource#match_in_owner_scope}.
|
10
|
+
#
|
11
|
+
# caCORE alert = caCORE does not enforce reference identity integrity, i.e. a search on object _a_
|
12
|
+
# with database record references _a_ => _b_ => _a_, the search result might be _a_ => _b_ => _a'_,
|
13
|
+
# where _a.identifier_ == _a'.identifier_. This visit method remedies this caCORE defect by matching
|
14
|
+
# source references on a previously matched identifier where possible.
|
15
|
+
#
|
16
|
+
# @param [<Jinx::Resource>] sources the domain objects to match against
|
17
|
+
# @param [<Jinx::Resource>] targets the domain objects to match
|
18
|
+
# @param [Resource] from the visiting domain object
|
19
|
+
# @param [Symbol] attribute the visiting attribute
|
20
|
+
# @return [{Jinx::Resource => Jinx::Resource}] the source => target matches
|
21
|
+
def match(sources, targets, from, attribute)
|
22
|
+
if sources.empty? or targets.empty? then
|
23
|
+
Hash::EMPTY_HASH
|
24
|
+
elsif from.class.property(attribute).owner? then
|
25
|
+
match_owner(sources.first, targets.first, from, attribute)
|
26
|
+
else
|
27
|
+
match_fetched(sources, targets)
|
28
|
+
end
|
11
29
|
end
|
12
30
|
|
13
|
-
alias :call :match
|
14
|
-
|
15
31
|
private
|
32
|
+
|
33
|
+
# @param [<Jinx::Resource>] source the fetched owner to match against
|
34
|
+
# @param [<Jinx::Resource>] target the owner to match
|
35
|
+
# @param [Resource] dependent the visiting dependent
|
36
|
+
# @param attribute (see #match)
|
37
|
+
# @return [{Resource => Resource}] the source => target singleton hash, if the source
|
38
|
+
# and target identifiers don't conflict, otherwise an empty hash
|
39
|
+
def match_owner(source, target, dependent, attribute)
|
40
|
+
return Hash::EMPTY_HASH unless source.minimal_match?(target)
|
41
|
+
logger.debug { "Matched #{dependent} #{attribute} owner #{source} to #{target}." }
|
42
|
+
{source => target}
|
43
|
+
end
|
16
44
|
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# the non-owner secondary key domain attributes
|
28
|
-
attrs = klass.secondary_key_attributes.select do |attr|
|
29
|
-
attr_md = klass.attribute_metadata(attr)
|
30
|
-
attr_md.domain? and not attr_md.owner?
|
31
|
-
end
|
32
|
-
|
33
|
-
# fetch the non-owner secondary key domain attributes as necessary
|
34
|
-
unless attrs.empty? then
|
35
|
-
sources.each do |src|
|
36
|
-
attrs.each do |attr|
|
37
|
-
next if src.send(attr)
|
38
|
-
logger.debug { "Fetching #{src.qp} #{attr} in order to match on the secondary key..." }
|
39
|
-
ref = src.query(attr).first || next
|
40
|
-
src.set_attribute(attr, ref)
|
41
|
-
logger.debug { "Set fetched #{src.qp} secondary key attribute #{attr} to fetched #{ref}." }
|
42
|
-
end
|
45
|
+
# Fetches the given domain objects non-owner secondary key domain attributes as necessary.
|
46
|
+
def fetch_secondary_key_references(sources)
|
47
|
+
# fetch the secondary key reference if necessary
|
48
|
+
sources.each do |src|
|
49
|
+
src.class.secondary_key_non_owner_domain_attributes.each do |pa|
|
50
|
+
next if src.send(pa)
|
51
|
+
logger.debug { "Fetching #{src.qp} #{pa} in order to match on the secondary key..." }
|
52
|
+
ref = src.query(pa).first || next
|
53
|
+
src.set_property_value(pa, ref)
|
54
|
+
logger.debug { "Set fetched #{src.qp} secondary key attribute #{pa} to fetched #{ref}." }
|
43
55
|
end
|
44
56
|
end
|
45
|
-
|
57
|
+
end
|
58
|
+
|
59
|
+
def match_fetched(sources, targets)
|
60
|
+
# complete the key
|
61
|
+
fetch_secondary_key_references(sources)
|
46
62
|
# match source => target based on the secondary key
|
47
63
|
unmatched = Set === sources ? sources.dup : sources.to_set
|
48
64
|
matches = {}
|
49
65
|
targets.each do |tgt|
|
50
|
-
src = tgt.match_in_owner_scope(unmatched)
|
51
|
-
next unless src
|
66
|
+
src = tgt.match_in_owner_scope(unmatched) || next
|
52
67
|
matches[src] = tgt
|
53
68
|
unmatched.delete(src)
|
54
69
|
end
|
55
|
-
|
56
70
|
matches
|
57
71
|
end
|
58
72
|
end
|