caruby-core 1.4.3 → 1.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -1
- data/README.md +4 -5
- data/lib/caruby/domain/merge.rb +7 -1
- data/lib/caruby/domain/resource_attributes.rb +2 -2
- data/lib/caruby/domain/uniquify.rb +12 -0
- data/lib/caruby/migration/migrator.rb +62 -14
- data/lib/caruby/migration/uniquify.rb +4 -6
- data/lib/caruby/resource.rb +6 -5
- data/lib/caruby/version.rb +1 -1
- metadata +2 -2
data/History.txt
CHANGED
@@ -6,13 +6,18 @@
|
|
6
6
|
|
7
7
|
* Minor Migrator fixes
|
8
8
|
|
9
|
-
|
10
9
|
=== 1.4.3 / 2011-02-18
|
11
10
|
|
12
11
|
* Refactor Persistifier
|
13
12
|
|
14
13
|
* Add attribute filters
|
15
14
|
|
15
|
+
=== 1.4.4 / 2011-02-25
|
16
|
+
|
17
|
+
* Support default migration option
|
18
|
+
|
19
|
+
* Merge nondomain collection value properly.
|
20
|
+
|
16
21
|
|
17
22
|
|
18
23
|
|
data/README.md
CHANGED
@@ -4,10 +4,10 @@ caRuby: Simplifying caBIG(TM)
|
|
4
4
|
**Home**: [http://caruby.rubyforge.org](http://caruby.rubyforge.org)
|
5
5
|
**Git**: [http://github.com/caruby/core](http://github.com/caruby/core)
|
6
6
|
**Author**: OHSU Knight Cancer Institute
|
7
|
-
**Copyright**: 2010
|
7
|
+
**Copyright**: 2010, 2011
|
8
8
|
**License**: MIT License
|
9
9
|
**Latest Version**: 1.4.1
|
10
|
-
**Release Date**:
|
10
|
+
**Release Date**: February 25th 2011
|
11
11
|
|
12
12
|
Synopsis
|
13
13
|
--------
|
@@ -40,12 +40,11 @@ See the project [Home](http://caruby.rubyforge.org) Page for usage examples.
|
|
40
40
|
Changelog
|
41
41
|
---------
|
42
42
|
|
43
|
-
-
|
44
|
-
- Initial public release
|
43
|
+
- See +History.txt+
|
45
44
|
|
46
45
|
Copyright
|
47
46
|
---------
|
48
47
|
|
49
|
-
caRuby © 2010 by [Oregon Health & Science University](http://www.ohsu.edu/xd/health/services/cancer/index.cfm).
|
48
|
+
caRuby © 2010, 2011 by [Oregon Health & Science University](http://www.ohsu.edu/xd/health/services/cancer/index.cfm).
|
50
49
|
caRuby is licensed under the MIT license. Please see the LICENSE and LEGAL
|
51
50
|
files for more information.
|
data/lib/caruby/domain/merge.rb
CHANGED
@@ -95,7 +95,13 @@ module CaRuby
|
|
95
95
|
|
96
96
|
# @see #merge_attribute
|
97
97
|
def merge_nondomain_attribute_value(attr_md, oldval, newval)
|
98
|
-
oldval.nil?
|
98
|
+
if oldval.nil? then
|
99
|
+
send(attr_md.writer, newval)
|
100
|
+
elsif attr_md.collection? then
|
101
|
+
oldval.merge(newval)
|
102
|
+
else
|
103
|
+
oldval
|
104
|
+
end
|
99
105
|
end
|
100
106
|
|
101
107
|
# @see #merge_attribute
|
@@ -167,8 +167,8 @@ module CaRuby
|
|
167
167
|
@px_cscd_attrs ||= cascaded_attributes.compose { |attr_md| attr_md.proxied_save? }
|
168
168
|
end
|
169
169
|
|
170
|
-
# @return [<Symbol>] the {#cascaded_attributes} which
|
171
|
-
#
|
170
|
+
# @return [<Symbol>] the {#cascaded_attributes} which do not have a
|
171
|
+
# #{AttributeMetadata#proxied_save?}
|
172
172
|
def unproxied_cascaded_attributes
|
173
173
|
@unpx_cscd_attrs ||= cascaded_attributes.compose { |attr_md| not attr_md.proxied_save? }
|
174
174
|
end
|
@@ -15,6 +15,18 @@ module CaRuby
|
|
15
15
|
end
|
16
16
|
ResourceUniquifier.instance.uniquify(self, value)
|
17
17
|
end
|
18
|
+
|
19
|
+
# Makes the secondary key unique by replacing each String key attribute value
|
20
|
+
# with a unique value.
|
21
|
+
def uniquify
|
22
|
+
self.class.secondary_key_attributes.each do |attr|
|
23
|
+
oldval = send(attr)
|
24
|
+
next unless String === oldval
|
25
|
+
newval = uniquify_value(oldval)
|
26
|
+
set_attribute(attr, newval)
|
27
|
+
logger.debug { "Reset #{qp} #{attr} from #{oldval} to unique value #{newval}." }
|
28
|
+
end
|
29
|
+
end
|
18
30
|
end
|
19
31
|
end
|
20
32
|
|
@@ -16,6 +16,7 @@ require 'caruby/util/pretty_print'
|
|
16
16
|
require 'caruby/util/properties'
|
17
17
|
require 'caruby/util/collection'
|
18
18
|
require 'caruby/migration/migratable'
|
19
|
+
require 'caruby/migration/uniquify'
|
19
20
|
|
20
21
|
module CaRuby
|
21
22
|
class MigrationError < RuntimeError; end
|
@@ -30,8 +31,11 @@ module CaRuby
|
|
30
31
|
# @option opts [String] :database required application {CaRuby::Database}
|
31
32
|
# @option opts [String] :target required target domain class
|
32
33
|
# @option opts [String] :mapping required input field => caTissue attribute mapping file
|
34
|
+
# @option opts [String] :defaults optional caTissue attribute => value default mapping file
|
33
35
|
# @option opts [String] :input required source file to migrate
|
34
36
|
# @option opts [String] :shims optional array of shim files to load
|
37
|
+
# @option opts [String] :unique ensures that migrated objects which include the {Resource::Unique}
|
38
|
+
# mix-in do not conflict with existing or future objects (used for testing)
|
35
39
|
# @option opts [String] :bad optional invalid record file
|
36
40
|
# @option opts [Integer] :offset zero-based starting source record number to process (default 0)
|
37
41
|
# @option opts [Boolean] :quiet suppress output messages
|
@@ -67,6 +71,8 @@ module CaRuby
|
|
67
71
|
|
68
72
|
private
|
69
73
|
|
74
|
+
UNIQUIFY_SHIM = File.join(File.dirname(__FILE__), 'uniquify.rb')
|
75
|
+
|
70
76
|
# Class {#migrate} with a {#save} block.
|
71
77
|
def execute_save
|
72
78
|
@database.open do |db|
|
@@ -95,6 +101,7 @@ module CaRuby
|
|
95
101
|
def parse_options(opts)
|
96
102
|
@fld_map_file = opts[:mapping]
|
97
103
|
raise MigrationError.new("Migrator missing required field mapping file parameter") if @fld_map_file.nil?
|
104
|
+
@def_file = opts[:defaults]
|
98
105
|
@shims = opts[:shims] ||= []
|
99
106
|
@offset = opts[:offset] ||= 0
|
100
107
|
@input = Options.get(:input, opts)
|
@@ -121,8 +128,10 @@ module CaRuby
|
|
121
128
|
|
122
129
|
# create the class => path => header hash
|
123
130
|
fld_map = load_field_map(@fld_map_file)
|
131
|
+
# create the class => path => default value hash
|
132
|
+
@def_hash = @def_file ? load_defaults(@def_file) : {}
|
124
133
|
# create the class => paths hash
|
125
|
-
@cls_paths_hash = create_class_paths_hash(fld_map)
|
134
|
+
@cls_paths_hash = create_class_paths_hash(fld_map, @def_hash)
|
126
135
|
# create the path => class => header hash
|
127
136
|
@header_map = create_header_map(fld_map)
|
128
137
|
# add missing owner classes (copy the keys rather than using each_key since the hash is updated)
|
@@ -143,7 +152,7 @@ module CaRuby
|
|
143
152
|
# the class => attribute migration methods hash
|
144
153
|
create_migration_method_hashes
|
145
154
|
|
146
|
-
#
|
155
|
+
# Collect the String input fields for the custom CSVLoader converter.
|
147
156
|
@nonstring_headers = Set.new
|
148
157
|
logger.info("Migration attributes:")
|
149
158
|
@header_map.each do |path, cls_hdr_hash|
|
@@ -376,15 +385,25 @@ module CaRuby
|
|
376
385
|
#
|
377
386
|
# @param [Class] klass
|
378
387
|
# @param [{Symbol => Object}] row the input row
|
379
|
-
# @param [<Resource>] the migrated instances for this row
|
380
|
-
# @return the new
|
388
|
+
# @param [<Resource>] created the migrated instances for this row
|
389
|
+
# @return [Resource] the new instance
|
381
390
|
def create(klass, row, created)
|
382
391
|
# the new object
|
383
392
|
created << obj = klass.new
|
393
|
+
migrate_attributes(obj, row, created)
|
394
|
+
add_defaults(obj, row, created)
|
395
|
+
logger.debug { "Migrator created #{obj}." }
|
396
|
+
obj
|
397
|
+
end
|
398
|
+
|
399
|
+
# @param [Resource] the migration object
|
400
|
+
# @param row (see #create)
|
401
|
+
# @param [<Resource>] created (see #create)
|
402
|
+
def migrate_attributes(obj, row, created)
|
384
403
|
# for each input header which maps to a migratable target attribute metadata path,
|
385
404
|
# set the target attribute, creating intermediate objects as needed.
|
386
|
-
@cls_paths_hash[
|
387
|
-
header = @header_map[path][
|
405
|
+
@cls_paths_hash[obj.class].each do |path|
|
406
|
+
header = @header_map[path][obj.class]
|
388
407
|
# the input value
|
389
408
|
value = row[header]
|
390
409
|
next if value.nil?
|
@@ -393,8 +412,18 @@ module CaRuby
|
|
393
412
|
# set the attribute
|
394
413
|
migrate_attribute(ref, path.last, value, row)
|
395
414
|
end
|
396
|
-
|
397
|
-
|
415
|
+
end
|
416
|
+
|
417
|
+
# @param [Resource] the migration object
|
418
|
+
# @param row (see #create)
|
419
|
+
# @param [<Resource>] created (see #create)
|
420
|
+
def add_defaults(obj, row, created)
|
421
|
+
@def_hash[obj.class].each do |path, value|
|
422
|
+
# fill the reference path
|
423
|
+
ref = fill_path(obj, path[0...-1], row, created)
|
424
|
+
# set the attribute to the default value unless there is already a value
|
425
|
+
ref.merge_attribute(path.last.to_sym, value)
|
426
|
+
end
|
398
427
|
end
|
399
428
|
|
400
429
|
# Fills the given reference AttributeMetadata path starting at obj.
|
@@ -472,7 +501,7 @@ module CaRuby
|
|
472
501
|
next if attr_list.blank?
|
473
502
|
# the header accessor method for the field
|
474
503
|
header = @loader.accessor(field)
|
475
|
-
raise MigrationError.new("Field defined in migration configuration not found: #{field}") if header.nil?
|
504
|
+
raise MigrationError.new("Field defined in migration configuration #{file} not found in input file #{@input} headers: #{field}") if header.nil?
|
476
505
|
attr_list.split(/,\s*/).each do |path_s|
|
477
506
|
klass, path = create_attribute_path(path_s)
|
478
507
|
map[klass][path] = header
|
@@ -499,11 +528,29 @@ module CaRuby
|
|
499
528
|
end
|
500
529
|
map
|
501
530
|
end
|
531
|
+
|
532
|
+
def load_defaults(file)
|
533
|
+
# load the field mapping config file
|
534
|
+
begin
|
535
|
+
config = YAML::load_file(file)
|
536
|
+
rescue
|
537
|
+
raise MigrationError.new("Could not read defaults file #{file}: " + $!)
|
538
|
+
end
|
502
539
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
540
|
+
# collect the class => path => value entries
|
541
|
+
map = LazyHash.new { Hash.new }
|
542
|
+
config.each do |path_s, value|
|
543
|
+
next if value.blank?
|
544
|
+
klass, path = create_attribute_path(path_s)
|
545
|
+
map[klass][path] = value
|
546
|
+
end
|
547
|
+
|
548
|
+
map
|
549
|
+
end
|
550
|
+
|
551
|
+
# @param [String] path_s a period-delimited path string path_s in the form _class_(._attribute_)+
|
552
|
+
# @return [<AttributeMetadata>] the corresponding attribute metadata path
|
553
|
+
# @raise [MigrationError] if the path string is malformed or an attribute is not found
|
507
554
|
def create_attribute_path(path_s)
|
508
555
|
names = path_s.split('.')
|
509
556
|
# if the path starts with a capitalized class name, then resolve the class.
|
@@ -529,9 +576,10 @@ module CaRuby
|
|
529
576
|
end
|
530
577
|
|
531
578
|
# @return a new class => [paths] hash from the migration fields configuration map
|
532
|
-
def create_class_paths_hash(fld_map)
|
579
|
+
def create_class_paths_hash(fld_map, def_map)
|
533
580
|
hash = {}
|
534
581
|
fld_map.each { |klass, path_hdr_hash| hash[klass] = path_hdr_hash.keys.to_set }
|
582
|
+
def_map.each { |klass, path_val_hash| (hash[klass] ||= Set.new).merge(path_val_hash.keys) }
|
535
583
|
hash
|
536
584
|
end
|
537
585
|
|
@@ -2,14 +2,12 @@ require 'caruby/migration/migratable'
|
|
2
2
|
require 'caruby/domain/uniquify'
|
3
3
|
|
4
4
|
module CaRuby
|
5
|
-
module
|
6
|
-
# Unique makes a Migratable Resource domain object unique within the scope its class.
|
5
|
+
module Resource
|
7
6
|
module Unique
|
8
|
-
|
9
|
-
|
10
|
-
# Augments the migration by making this Resource object unique in the scope of its class.
|
7
|
+
# Augments this {Unique} mix-in with a {Migratable#migrate} method which calls {Unique#uniquify}
|
8
|
+
# to make this Resource object unique in the scope of its class.
|
11
9
|
#
|
12
|
-
# @param (see
|
10
|
+
# @param (see Migratable#migrate)
|
13
11
|
def migrate(row, migrated)
|
14
12
|
super
|
15
13
|
logger.debug { "Migrator making #{self} unique..." }
|
data/lib/caruby/resource.rb
CHANGED
@@ -39,12 +39,12 @@ module CaRuby
|
|
39
39
|
self
|
40
40
|
end
|
41
41
|
|
42
|
-
# Validates this domain object and its
|
43
|
-
# database create or update operation.
|
42
|
+
# Validates this domain object and its #{ResourceAttributes.unproxied_cascaded_attributes}
|
43
|
+
# for completeness prior to a database create or update operation.
|
44
44
|
# The object is valid if it contains a non-nil value for each mandatory property.
|
45
45
|
# Objects which have already been validated are skipped.
|
46
|
-
# Returns this domain object.
|
47
46
|
#
|
47
|
+
# @return [Resource] this domain object
|
48
48
|
# @raise [ValidationError] if a mandatory attribute value is missing
|
49
49
|
def validate
|
50
50
|
unless @validated then
|
@@ -55,7 +55,7 @@ module CaRuby
|
|
55
55
|
raise ValidationError.new("Required attribute value missing for #{self}: #{invalid.join(', ')}")
|
56
56
|
end
|
57
57
|
end
|
58
|
-
self.class.
|
58
|
+
self.class.unproxied_cascaded_attributes.each do |attr|
|
59
59
|
send(attr).enumerate { |dep| dep.validate }
|
60
60
|
end
|
61
61
|
@validated = true
|
@@ -330,7 +330,7 @@ module CaRuby
|
|
330
330
|
# @return [Boolean] whether this Resource equals other
|
331
331
|
def minimal_match?(other)
|
332
332
|
self.class === other and
|
333
|
-
|
333
|
+
(identifier.nil? or other.identifier.nil? or identifier == other.identifier)
|
334
334
|
end
|
335
335
|
|
336
336
|
# Returns an enumerator on the transitive closure of the reference attributes.
|
@@ -649,6 +649,7 @@ module CaRuby
|
|
649
649
|
# @return [Boolean] whether the other domain object matches this domain object on a
|
650
650
|
# secondary key without owner attributes
|
651
651
|
def match_without_owner_attribute?(other)
|
652
|
+
return unless other.class == self.class
|
652
653
|
oattrs = self.class.owner_attributes
|
653
654
|
return if oattrs.empty?
|
654
655
|
# match on the secondary key
|
data/lib/caruby/version.rb
CHANGED