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.
Files changed (126) hide show
  1. data/Gemfile +9 -0
  2. data/History.md +5 -1
  3. data/lib/caruby.rb +3 -5
  4. data/lib/caruby/caruby-src.tar.gz +0 -0
  5. data/lib/caruby/database.rb +53 -69
  6. data/lib/caruby/database/application_service.rb +25 -0
  7. data/lib/caruby/database/cache.rb +60 -0
  8. data/lib/caruby/database/fetched_matcher.rb +52 -38
  9. data/lib/caruby/database/lazy_loader.rb +4 -4
  10. data/lib/caruby/database/operation.rb +34 -0
  11. data/lib/caruby/database/persistable.rb +171 -86
  12. data/lib/caruby/database/persistence_service.rb +32 -34
  13. data/lib/caruby/database/persistifier.rb +100 -43
  14. data/lib/caruby/database/reader.rb +107 -85
  15. data/lib/caruby/database/reader_template_builder.rb +60 -0
  16. data/lib/caruby/database/saved_matcher.rb +3 -3
  17. data/lib/caruby/database/sql_executor.rb +88 -17
  18. data/lib/caruby/database/writer.rb +213 -177
  19. data/lib/caruby/database/writer_template_builder.rb +334 -0
  20. data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
  21. data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
  22. data/lib/caruby/{util → helpers}/person.rb +3 -3
  23. data/lib/caruby/{util → helpers}/properties.rb +7 -9
  24. data/lib/caruby/{util → helpers}/roman.rb +2 -2
  25. data/lib/caruby/{util → helpers}/version.rb +1 -1
  26. data/lib/caruby/json/deserializer.rb +2 -2
  27. data/lib/caruby/json/serializer.rb +49 -7
  28. data/lib/caruby/metadata.rb +30 -0
  29. data/lib/caruby/metadata/java_property.rb +21 -0
  30. data/lib/caruby/metadata/propertied.rb +191 -0
  31. data/lib/caruby/metadata/property.rb +22 -0
  32. data/lib/caruby/metadata/property_characteristics.rb +201 -0
  33. data/lib/caruby/migration/migratable.rb +11 -182
  34. data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
  35. data/lib/caruby/resource.rb +20 -823
  36. data/lib/caruby/version.rb +1 -1
  37. data/test/lib/caruby/database/cache_test.rb +54 -0
  38. data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
  39. data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
  40. data/test/lib/caruby/helpers/properties_test.rb +34 -0
  41. data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
  42. data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
  43. data/test/lib/helper.rb +7 -0
  44. metadata +161 -214
  45. data/lib/caruby/cli/application.rb +0 -36
  46. data/lib/caruby/cli/command.rb +0 -202
  47. data/lib/caruby/csv/csv_mapper.rb +0 -159
  48. data/lib/caruby/csv/csvio.rb +0 -203
  49. data/lib/caruby/database/search_template_builder.rb +0 -56
  50. data/lib/caruby/database/store_template_builder.rb +0 -278
  51. data/lib/caruby/domain.rb +0 -193
  52. data/lib/caruby/domain/attribute.rb +0 -584
  53. data/lib/caruby/domain/attributes.rb +0 -628
  54. data/lib/caruby/domain/dependency.rb +0 -225
  55. data/lib/caruby/domain/id_alias.rb +0 -22
  56. data/lib/caruby/domain/importer.rb +0 -183
  57. data/lib/caruby/domain/introspection.rb +0 -176
  58. data/lib/caruby/domain/inverse.rb +0 -172
  59. data/lib/caruby/domain/inversible.rb +0 -90
  60. data/lib/caruby/domain/java_attribute.rb +0 -173
  61. data/lib/caruby/domain/merge.rb +0 -185
  62. data/lib/caruby/domain/metadata.rb +0 -142
  63. data/lib/caruby/domain/mixin.rb +0 -35
  64. data/lib/caruby/domain/properties.rb +0 -95
  65. data/lib/caruby/domain/reference_visitor.rb +0 -428
  66. data/lib/caruby/domain/uniquify.rb +0 -50
  67. data/lib/caruby/import/java.rb +0 -387
  68. data/lib/caruby/migration/migrator.rb +0 -918
  69. data/lib/caruby/migration/resource_module.rb +0 -9
  70. data/lib/caruby/migration/uniquify.rb +0 -17
  71. data/lib/caruby/util/attribute_path.rb +0 -44
  72. data/lib/caruby/util/cache.rb +0 -56
  73. data/lib/caruby/util/class.rb +0 -149
  74. data/lib/caruby/util/collection.rb +0 -1152
  75. data/lib/caruby/util/domain_extent.rb +0 -46
  76. data/lib/caruby/util/file_separator.rb +0 -65
  77. data/lib/caruby/util/inflector.rb +0 -27
  78. data/lib/caruby/util/log.rb +0 -95
  79. data/lib/caruby/util/math.rb +0 -12
  80. data/lib/caruby/util/merge.rb +0 -59
  81. data/lib/caruby/util/module.rb +0 -18
  82. data/lib/caruby/util/options.rb +0 -97
  83. data/lib/caruby/util/partial_order.rb +0 -35
  84. data/lib/caruby/util/pretty_print.rb +0 -204
  85. data/lib/caruby/util/stopwatch.rb +0 -74
  86. data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
  87. data/lib/caruby/util/transitive_closure.rb +0 -55
  88. data/lib/caruby/util/tree.rb +0 -48
  89. data/lib/caruby/util/trie.rb +0 -37
  90. data/lib/caruby/util/uniquifier.rb +0 -30
  91. data/lib/caruby/util/validation.rb +0 -20
  92. data/lib/caruby/util/visitor.rb +0 -365
  93. data/lib/caruby/util/weak_hash.rb +0 -36
  94. data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
  95. data/test/lib/caruby/csv/csvio_test.rb +0 -69
  96. data/test/lib/caruby/database/persistable_test.rb +0 -92
  97. data/test/lib/caruby/domain/domain_test.rb +0 -112
  98. data/test/lib/caruby/domain/inversible_test.rb +0 -99
  99. data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
  100. data/test/lib/caruby/import/java_test.rb +0 -80
  101. data/test/lib/caruby/import/mixed_case_test.rb +0 -14
  102. data/test/lib/caruby/migration/test_case.rb +0 -102
  103. data/test/lib/caruby/test_case.rb +0 -230
  104. data/test/lib/caruby/util/cache_test.rb +0 -23
  105. data/test/lib/caruby/util/class_test.rb +0 -61
  106. data/test/lib/caruby/util/collection_test.rb +0 -398
  107. data/test/lib/caruby/util/command_test.rb +0 -55
  108. data/test/lib/caruby/util/domain_extent_test.rb +0 -60
  109. data/test/lib/caruby/util/file_separator_test.rb +0 -30
  110. data/test/lib/caruby/util/inflector_test.rb +0 -12
  111. data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
  112. data/test/lib/caruby/util/merge_test.rb +0 -83
  113. data/test/lib/caruby/util/module_test.rb +0 -25
  114. data/test/lib/caruby/util/options_test.rb +0 -59
  115. data/test/lib/caruby/util/partial_order_test.rb +0 -42
  116. data/test/lib/caruby/util/pretty_print_test.rb +0 -85
  117. data/test/lib/caruby/util/properties_test.rb +0 -50
  118. data/test/lib/caruby/util/stopwatch_test.rb +0 -18
  119. data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
  120. data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
  121. data/test/lib/caruby/util/tree_test.rb +0 -23
  122. data/test/lib/caruby/util/trie_test.rb +0 -14
  123. data/test/lib/caruby/util/visitor_test.rb +0 -278
  124. data/test/lib/caruby/util/weak_hash_test.rb +0 -45
  125. data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
  126. 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.5.4 / 2011-09-22
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 'caruby/resource'
2
-
3
- # CaRuby is a JRuby facade for the caBIG application APIs.
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
@@ -1,20 +1,17 @@
1
1
  require 'generator'
2
- require 'caruby/util/log'
3
- require 'caruby/util/collection'
4
- require 'caruby/util/validation'
5
- require 'caruby/util/options'
6
- require 'caruby/util/visitor'
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
- # Database CRUD operation.
48
- class Operation
49
- attr_reader :type, :subject, :attribute
50
-
51
- # @param [:find, :query, :create, :udate, :delete] type the database operation type
52
- # @param [Persistable] subject the domain object on which the operation is performed
53
- # @param [{Symbol => Object}, Symbol, nil] the operation characteristics
54
- # @option opts [Symbol] :attribute the query attribute
55
- # @option opts [Boolean] :autogenerated whether this is an auto-generated subject update
56
- def initialize(type, subject, opts=nil)
57
- @type = type
58
- @subject = subject
59
- @attribute = Options.get(:attribute, opts)
60
- @autogenerated = Options.get(:autogenerated, opts, false)
61
- end
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 domain object.
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
- # started on demand if necessary.
153
+ # Subclasses can override for specialized services. A session is started
154
+ # on demand if necessary.
168
155
  #
169
- # @param [Persistable, Class] obj the domain object or {Resource} class
170
- # @return [PersistanceService] the service for the domain object
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 raise ArgumentError.new("#{self} persistence_service argument is not a Class: {#klass.qp}") end
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
- attr = Options.get(:attribute, opts)
229
- attr_s = " #{attr}" if attr
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 raise DatabaseError.new('Application user option missing') end
266
- if @password.nil? then raise DatabaseError.new('Application password option missing') end
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}") and raise
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 'caruby/util/options'
2
- require 'caruby/util/collection'
1
+ require 'jinx/helpers/options'
2
+ require 'jinx/helpers/collection'
3
3
 
4
4
  module CaRuby
5
5
  class Database
6
- # Proc that matches fetched sources to targets.
6
+ # Helper that matches fetched sources to targets.
7
7
  class FetchedMatcher
8
- # Initializes a new FetchedMatcher.
9
- def match(srcs, tgts)
10
- match_fetched(srcs, tgts)
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
- # Returns a target => source match hash for the given targets and sources using
18
- # {Resource#match_in_owner_scope}.
19
- #
20
- # @param [<Resource>] sources the domain objects to match with targets
21
- # @param [<Resource>] targets the domain objects to match with targets
22
- # @return [{Resource => Resource}] the source => target matches
23
- def match_fetched(sources, targets)
24
- return Hash::EMPTY_HASH if sources.empty? or targets.empty?
25
- # the domain class
26
- klass = sources.first.class
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