caruby-core 1.5.5 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|