caruby-core 1.4.6 → 1.4.7

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,29 +1,33 @@
1
1
  === 1.4.1 / 2010-11-23
2
2
 
3
- * Initial public release
3
+ * Initial public release.
4
4
 
5
5
  === 1.4.2 / 2010-11-30
6
6
 
7
- * Minor Migrator fixes
7
+ * Minor Migrator fixes.
8
8
 
9
9
  === 1.4.3 / 2011-02-18
10
10
 
11
11
  * Refactor Persistifier
12
12
 
13
- * Add attribute filters
13
+ * Add attribute filters.
14
14
 
15
15
  === 1.4.4 / 2011-02-25
16
16
 
17
- * Support default migration option
17
+ * Support default migration option.
18
18
 
19
19
  * Merge nondomain collection value properly.
20
20
 
21
21
  === 1.4.5 / 2011-02-26
22
22
 
23
- * Fix default option
23
+ * Fix default option.
24
24
 
25
25
  === 1.4.6 / 2011-02-26
26
26
 
27
- * Upgrade to JRuby 1.5
27
+ * Upgrade to JRuby 1.5.
28
+
29
+ === 1.4.7 / 2011-03-04
30
+
31
+ * Support annotation migration.
28
32
 
29
33
 
data/README.md CHANGED
@@ -35,11 +35,6 @@ Usage
35
35
 
36
36
  See the project [Home](http://caruby.rubyforge.org) Page for usage examples.
37
37
 
38
- Changelog
39
- ---------
40
-
41
- - See +History.txt+
42
-
43
38
  Copyright
44
39
  ---------
45
40
 
@@ -4,12 +4,14 @@ require 'caruby/util/collection'
4
4
  module CaRuby
5
5
  class Database
6
6
  # Proc that matches fetched sources to targets.
7
- class FetchedMatcher < Proc
7
+ class FetchedMatcher
8
8
  # Initializes a new FetchedMatcher.
9
- def initialize
10
- super { |srcs, tgts| match_fetched(srcs, tgts) }
9
+ def match(srcs, tgts)
10
+ match_fetched(srcs, tgts)
11
11
  end
12
12
 
13
+ alias :call :match
14
+
13
15
  private
14
16
 
15
17
  # Returns a target => source match hash for the given targets and sources using
@@ -27,6 +29,7 @@ module CaRuby
27
29
  attr_md = klass.attribute_metadata(attr)
28
30
  attr_md.domain? and not attr_md.owner?
29
31
  end
32
+
30
33
  # fetch the non-owner secondary key domain attributes as necessary
31
34
  unless attrs.empty? then
32
35
  sources.each do |src|
@@ -39,6 +42,7 @@ module CaRuby
39
42
  end
40
43
  end
41
44
  end
45
+
42
46
  # match source => target based on the secondary key
43
47
  unmatched = Set === sources ? sources.dup : sources.to_set
44
48
  matches = {}
@@ -48,6 +52,7 @@ module CaRuby
48
52
  matches[src] = tgt
49
53
  unmatched.delete(src)
50
54
  end
55
+
51
56
  matches
52
57
  end
53
58
  end
@@ -93,7 +93,7 @@ module CaRuby
93
93
  return oldval if fetched.nil_or_empty?
94
94
  # merge the fetched into the attribute
95
95
  logger.debug { "Merging #{subject.qp} fetched #{attribute} value #{fetched.qp}#{' into ' + oldval.qp if oldval}..." }
96
- matches = @matcher.call(fetched.to_enum, oldval.to_enum)
96
+ matches = @matcher.match(fetched.to_enum, oldval.to_enum)
97
97
  subject.merge_attribute(attribute, fetched, matches)
98
98
  end
99
99
  end
@@ -23,8 +23,10 @@ module CaRuby
23
23
  @name = name
24
24
  ver_opt = opts[:version]
25
25
  @version = ver_opt.to_s.to_version if ver_opt
26
- @host = opts[:host] || "localhost"
26
+ @host = opts[:host] || default_host
27
+ @port = opts[:port] || 8080
27
28
  @timer = Stopwatch.new
29
+ logger.debug { "Created persistence service #{name} at #{@host}:#{@port}." }
28
30
  end
29
31
 
30
32
  ## Database access methods ##
@@ -81,7 +83,7 @@ module CaRuby
81
83
 
82
84
  # @return [ApplicationServiceProvider] the CaCORE service provider wrapped by this PersistenceService
83
85
  def app_service
84
- url = "http://#{@host}:8080/#{name}/http/remoteService"
86
+ url = "http://#{@host}:#{@port}/#{@name}/http/remoteService"
85
87
  logger.debug { "Connecting to service provider at #{url}..." }
86
88
  ApplicationServiceProvider.remote_instance(url)
87
89
  end
@@ -109,7 +111,17 @@ module CaRuby
109
111
  def dispatch
110
112
  time { yield app_service }
111
113
  end
112
-
114
+
115
+ # @return [String] the default host name
116
+ def default_host
117
+ # # TODO - extract from the service config file
118
+ # xml = JRuby.runtime.jruby_class_loader.getResourceAsStream('remoteService.xml')
119
+ # if xml then
120
+ # # parse xml file
121
+ # end
122
+ 'localhost'
123
+ end
124
+
113
125
  def query_hql(hql)
114
126
  logger.debug { "Building HQLCriteria..." }
115
127
  criteria = HQLCriteria.new(hql)
@@ -4,10 +4,6 @@ module CaRuby
4
4
  class Database
5
5
  # Proc that matches saved result sources to targets.
6
6
  class SavedMatcher < FetchedMatcher
7
- # Initializes a new SavedMatcher.
8
- def initialize
9
- super
10
- end
11
7
 
12
8
  private
13
9
 
@@ -549,6 +549,8 @@ module CaRuby
549
549
  #
550
550
  # @param (see #sync_saved)
551
551
  def sync_save_result(source, target)
552
+ # Bail if the result is the same as the source, as occurs, e.g., with caTissue annotations.
553
+ return if source == target
552
554
  logger.debug { "Synchronizing #{target} save result #{source} with the database..." }
553
555
  # If the target was created, then refetch and merge the source if necessary to reflect auto-generated
554
556
  # non-domain attribute values.
@@ -80,6 +80,7 @@ module CaRuby
80
80
  #
81
81
  # @param [String] service_name the name of the default {PersistenceService}
82
82
  # @param [{Symbol => String}] opts access options
83
+ # @option opts [String] :host application service host name
83
84
  # @option opts [String] :login application service login user
84
85
  # @option opts [String] :password application service login password
85
86
  # @example
@@ -91,9 +92,10 @@ module CaRuby
91
92
  @defaults = {}
92
93
  @user = Options.get(:user, opts)
93
94
  @password = Options.get(:password, opts)
94
- @host = Options.get(:host, opts)
95
+ host = Options.get(:host, opts)
96
+ port = Options.get(:port, opts)
95
97
  # class => service hash; default is the catissuecore app service
96
- @def_persist_svc = PersistenceService.new(service_name, :host => @host)
98
+ @def_persist_svc = PersistenceService.new(service_name, :host => host, :port => port)
97
99
  @persistence_services = [@def_persist_svc].to_set
98
100
  @cls_svc_hash = Hash.new(@def_persist_svc)
99
101
  # the create/update nested operations
@@ -73,6 +73,8 @@ module CaRuby
73
73
  def java_attributes
74
74
  @java_attrs ||= attribute_filter { |attr_md| attr_md.java_property? }
75
75
  end
76
+
77
+ alias :printable_attributes :java_attributes
76
78
 
77
79
  # @return [<Symbol>] the domain attributes
78
80
  def domain_attributes
@@ -366,19 +368,19 @@ module CaRuby
366
368
  # @yield [attr_md] the attribute selector
367
369
  # @yieldparam [AttributeMetadata] attr_md the candidate attribute
368
370
  def attribute_filter(&filter)
369
- Filter.new(@attr_md_hash, &filter)
371
+ Filter.new(@attr_md_hash, &filter)
370
372
  end
371
373
 
372
374
  # Initializes the attribute meta-data structures.
373
375
  def init_attributes
374
376
  @local_std_attr_hash = {}
375
- @alias_std_attr_map = append_parent_enum(@local_std_attr_hash) { |par| par.alias_standard_attribute_hash }
377
+ @alias_std_attr_map = append_ancestor_enum(@local_std_attr_hash) { |par| par.alias_standard_attribute_hash }
376
378
  @local_attr_md_hash = {}
377
- @attr_md_hash = append_parent_enum(@local_attr_md_hash) { |par| par.attribute_metadata_hash }
379
+ @attr_md_hash = append_ancestor_enum(@local_attr_md_hash) { |par| par.attribute_metadata_hash }
378
380
  @attributes = Enumerable::Enumerator.new(@attr_md_hash, :each_key)
379
381
  @local_mndty_attrs = Set.new
380
382
  @local_defaults = {}
381
- @defaults = append_parent_enum(@local_defaults) { |par| par.defaults }
383
+ @defaults = append_ancestor_enum(@local_defaults) { |par| par.defaults }
382
384
  end
383
385
 
384
386
  # Creates the given aliases to attributes.
@@ -472,9 +474,13 @@ module CaRuby
472
474
  @local_std_attr_hash[aliaz.to_sym] = std_attr
473
475
  end
474
476
 
475
- # Returns a new Enumerable which appends the evaluation of the given block in the parent
476
- # metadata context. The default enum is the evaluation of the given block on this Metadata.
477
- def append_parent_enum(enum)
477
+ # Appends to the given enumerable the result of evaluating the block given to this method
478
+ # on the superclass, if the superclass is a {Resource}.
479
+ #
480
+ # @param [Enumerable] enum the base collection
481
+ # @return [Enumerable] the {Enumerable#union} of the base collection with the superclass
482
+ # collection, if applicable
483
+ def append_ancestor_enum(enum)
478
484
  superclass < Resource ? enum.union(yield(superclass)) : enum
479
485
  end
480
486
 
@@ -506,7 +512,7 @@ module CaRuby
506
512
  # remove autogenerated or optional attributes
507
513
  mandatory.delete_if { |attr| attribute_metadata(attr).autogenerated? or attribute_metadata(attr).optional? }
508
514
  @local_mndty_attrs.merge!(mandatory)
509
- append_parent_enum(@local_mndty_attrs) { |par| par.mandatory_attributes }
515
+ append_ancestor_enum(@local_mndty_attrs) { |par| par.mandatory_attributes }
510
516
  end
511
517
 
512
518
  # Raises a NameError. Domain classes can override this method to dynamically create a new reference attribute.
@@ -10,6 +10,7 @@ module CaRuby
10
10
  [:user, "--user USER", "the application login user"],
11
11
  [:password, "--password PSWD", "the application login password"],
12
12
  [:host, "--host HOST", "the application host name"],
13
+ [:port, "--port PORT", "the application port number"],
13
14
  [:database_host, "--database_host HOST", "the database host name"],
14
15
  [:database_type, "--database_type TYPE", "the database type (mysql or oracle)"],
15
16
  [:database_driver, "--database_driver DRIVER", "the database driver"],
@@ -27,7 +28,7 @@ module CaRuby
27
28
  #
28
29
  # The application properties hash specifies the startup information,
29
30
  # including the user, password and application Java jar library path.
30
- # The properties are read from a property file. See CaRuby::Properties for
31
+ # The properties are read from a property file. See {Properties} for
31
32
  # more information.
32
33
  #
33
34
  # A Java class is imported into Ruby either by directly calling the
@@ -45,7 +46,9 @@ module CaRuby
45
46
  # ClinicalTrials::Participant.new
46
47
  # without defining the +Participant+ Ruby class.
47
48
  module ResourceModule
48
- # Adds the given klass to this ResourceModule. The class is extended with ResourceMetadata methods.
49
+ # Adds the given class to this ResourceModule. The class is extended with ResourceMetadata methods.
50
+ #
51
+ # @param [Class] the {Resource} class to add
49
52
  def add_class(klass)
50
53
  @rsc_classes ||= Set.new
51
54
  # add superclass if necessary
@@ -65,7 +68,7 @@ module CaRuby
65
68
  # @return [{Symbol => Object}] the caBIG application access properties
66
69
  # @see #load_access_properties
67
70
  def access_properties
68
- @resource_module__props ||= load_access_properties
71
+ @rsc_props ||= load_access_properties
69
72
  end
70
73
 
71
74
  # Loads the application start-up properties in the given file path.
@@ -97,7 +100,7 @@ module CaRuby
97
100
  raise ArgumentError.new("Application access properties file does not exist: #{file}")
98
101
  end
99
102
  # the access properties
100
- @resource_module__props ||= {}
103
+ props ||= {}
101
104
  # If no file was specified, then try the default.
102
105
  # If the default does not exist, then use the empty properties hash.
103
106
  # It is not an error to omit access properties, since the application domain classes
@@ -113,9 +116,8 @@ module CaRuby
113
116
  name = tokens.first.to_sym
114
117
  value = tokens.last
115
118
  # capture the property
116
- @resource_module__props[name] = value
119
+ props[name] = value
117
120
  end
118
-
119
121
  # Look for environment overrides preceded by the uppercase module name, e.g. CATISSUE
120
122
  # for the CaTissue module.
121
123
  env_prefix = name[/\w+$/].upcase
@@ -127,22 +129,22 @@ module CaRuby
127
129
  # the envvar value
128
130
  value = ENV[ev] || next
129
131
  # override the file property with the envar value
130
- @resource_module__props[opt] = value
132
+ props[opt] = value
131
133
  logger.info("Set application property #{opt} from environment variable #{ev}.")
132
134
  end
133
135
 
134
136
  # load the Java application jar path
135
137
  path_ev = "#{env_prefix}_PATH"
136
- path = ENV[path_ev] || @resource_module__props[:path]
138
+ path = ENV[path_ev] || props[:path]
137
139
  Java.add_path(path) if path
138
140
 
139
- @resource_module__props
141
+ props
140
142
  end
141
143
 
142
144
  # Loads the Ruby source files in the given directory.
143
145
  def load_dir(dir)
144
146
  # load the properties on demand
145
- load_access_properties if @resource_module__props.nil?
147
+ load_access_properties if @rsc_props.nil?
146
148
  # the domain class definitions
147
149
  sources = Dir.glob(File.join(dir, "*.rb"))
148
150
 
@@ -156,7 +158,7 @@ module CaRuby
156
158
  end
157
159
 
158
160
  # load the domain class definitions
159
- sym_file_hash.each do |sym, file|
161
+ sym_file_hash.to_a.each do |sym, file|
160
162
  require file
161
163
  end
162
164
 
@@ -168,6 +170,11 @@ module CaRuby
168
170
  logger.info("#{klass.pp_s}")
169
171
  end
170
172
  end
173
+
174
+ def java_import(klass)
175
+ # JRuby 1.4.x does not support a class argument
176
+ Class === klass ? super(klass.java_class.name) : super
177
+ end
171
178
 
172
179
  # Extends the mod module with Java class support. See the class documentation for details.
173
180
  #
@@ -199,7 +206,7 @@ module CaRuby
199
206
  #
200
207
  # The optional block overrides the native Java property access wrappers.
201
208
  # For example:
202
- # ClinicalTrials.java_import('edu.wustl.catissuecore.domain.Study') do
209
+ # ClinicalTrials.java_import Java::edu.wustl.catissuecore.domain.Study do
203
210
  # def study_code=(value)
204
211
  # value = value.to_s if Integer === value
205
212
  # setStudyCode(value)
@@ -271,6 +278,9 @@ module CaRuby
271
278
  # The captures are the trimmed property and value
272
279
  PROP_DEF_REGEX = /^(\w+)(?:\s*[:=]\s*)([^#]+)/
273
280
 
281
+ # @return [String] the default application properties file, given by +~/.+_name_,
282
+ # where _name_ is the underscore unqualified module name, e.g. +~/.catissue+
283
+ # for module +CaTissue+
274
284
  def default_properties_file
275
285
  home = ENV['HOME'] || '~'
276
286
  file = File.expand_path("#{home}/.#{name[/\w+$/].downcase}")
@@ -15,22 +15,36 @@ module Java
15
15
  # Adds the directories in the given path and all Java jar files contained in the directories
16
16
  # to the execution classpath.
17
17
  #
18
- # @param path the colon or semi-colon separated directories
18
+ # @param [String] path the colon or semi-colon separated directories
19
19
  def self.add_path(path)
20
20
  # the path directories
21
21
  dirs = path.split(/[:;]/).map { |dir| File.expand_path(dir) }
22
22
  # Add all jars found anywhere within the directories to the the classpath.
23
23
  add_jars(*dirs)
24
24
  # Add the directories to the the classpath.
25
- dirs.each { |dir| $CLASSPATH << dir }
25
+ dirs.each { |dir| add_to_classpath(dir) }
26
26
  end
27
27
 
28
28
  # Adds the jars in the directories to the execution class path.
29
29
  #
30
- # @param directories the directories containing jars to add
30
+ # @param [<String>] directories the directories containing jars to add
31
31
  def self.add_jars(*directories)
32
32
  directories.each do |dir|
33
- Dir[File.join(dir , "**", "*.jar")].each { |jar| $CLASSPATH << jar }
33
+ Dir[File.join(dir , "**", "*.jar")].each { |jar| add_to_classpath(jar) }
34
+ end
35
+ end
36
+
37
+ # Adds the given jar file or directory to the classpath.
38
+ #
39
+ # @param [String] file the jar file or directory to add
40
+ def self.add_to_classpath(file)
41
+ if file =~ /.jar$/ then
42
+ # require is preferred to classpath append for a jar file
43
+ require file
44
+ else
45
+ # A directory must end in a slash since JRuby uses an URLClassLoader.
46
+ if File.directory?(file) and not file =~ /\/$/ then file = file + '/' end
47
+ $CLASSPATH << file
34
48
  end
35
49
  end
36
50
 
@@ -118,55 +118,49 @@ module CaRuby
118
118
  # @param [{Symbol => String}] mth_hash a hash that associates this domain object's
119
119
  # attributes to migration method names
120
120
  def migrate_references(row, migrated, mth_hash=nil)
121
- self.class.saved_independent_attributes.each do |attr|
122
- ref = migratable__reference_value(attr, migrated)
123
- migratable__set_reference(attr, ref, row, mth_hash) if ref
124
- end
125
- self.class.unidirectional_dependent_attributes.each do |attr|
126
- ref = migratable__reference_value(attr, migrated)
127
- migratable__set_reference(attr, ref, row, mth_hash) if ref
128
- end
121
+ migratable__set_references(self.class.saved_independent_attributes, row, migrated, mth_hash)
122
+ migratable__set_references(self.class.unidirectional_dependent_attributes, row, migrated, mth_hash)
129
123
  end
130
124
 
131
125
  private
132
126
 
133
- # @param [Symbol] attribute the reference attribute to get
127
+ # @param [AttributeMetadata::Filter] the attributes to set
128
+ # @param row (see #migrate_references)
129
+ # @param migrated (see #migrate_references)
130
+ # @param mth_hash (see #migrate_references)
131
+ def migratable__set_references(attr_filter, row, migrated, mth_hash=nil)
132
+ attr_filter.each_pair do |attr, attr_md|
133
+ # the target value
134
+ ref = migratable__target_value(attr_md, row, migrated, mth_hash) || next
135
+ if attr_md.collection? then
136
+ # the current value
137
+ value = send(attr_md.reader) || next
138
+ value << ref
139
+ logger.debug { "Added migrated #{ref.qp} to #{qp} #{attribute}." }
140
+ else
141
+ set_attribute(attr, ref)
142
+ logger.debug { "Set #{qp} #{attr} to migrated #{ref.qp}." }
143
+ end
144
+ end
145
+ end
146
+
147
+ # @param [AttributeMetadata] attr_md the reference attribute
148
+ # @param row (see #migrate_references)
134
149
  # @param migrated (see #migrate_references)
135
- # @return [Resource, nil] the migrated value to which the attribute will be set
136
- def migratable__reference_value(attribute, migrated)
137
- # skip non-nil attributes
138
- return if send(attribute)
139
- # the attribute metadata, used for type information
140
- attr_md = self.class.attribute_metadata(attribute)
141
- # skip collection attributes
142
- return if attr_md.collection?
150
+ # @param mth_hash (see #migrate_references)
151
+ # @return [Resource, nil] the migrated instance of the given class, or nil if there is not
152
+ # exactly one such instance
153
+ def migratable__target_value(attr_md, row, migrated, mth_hash=nil)
143
154
  # the migrated references which are instances of the attribute type
144
155
  refs = migrated.select { |other| other != self and attr_md.type === other }
145
156
  # skip ambiguous references
146
157
  return unless refs.size == 1
147
158
  # the single reference
148
159
  ref = refs.first
149
- end
150
-
151
- # Sets the given migrated domain object attribute to the given reference.
152
- #
153
- # If the attribute is associated to a method in mth_hash, then that method is called on
154
- # the migrated instance and input row. The attribute is set to the method return value.
155
- # mth_hash includes an entry for each +migrate_+_attribute_ method defined by this
156
- # Resource's class.
157
- #
158
- # @param [Symbol] (see #migratable__reference_value)
159
- # @param [Resource] ref the migrated reference
160
- # @param row (see #migrate_references)
161
- # @param mth_hash (see #migrate_references)
162
- def migratable__set_reference(attribute, ref, row, mth_hash=nil)
163
- # the shim method, if any
164
- mth = mth_hash[attribute] if mth_hash
160
+ # the shim method, if any
161
+ mth = mth_hash[attr_md.to_sym] if mth_hash
165
162
  # if there is a shim method, then call it
166
- ref = send(mth, ref, row) if mth and respond_to?(mth)
167
- return if ref.nil?
168
- logger.debug { "Setting #{qp} #{attribute} to migrated #{ref.qp}..." }
169
- set_attribute(attribute, ref)
163
+ mth && respond_to?(mth) ? send(mth, ref, row) : ref
170
164
  end
171
165
  end
172
166
  end
@@ -99,6 +99,7 @@ module CaRuby
99
99
  end
100
100
 
101
101
  def parse_options(opts)
102
+ logger.debug { "Migrator options: #{opts.qp}" }
102
103
  @fld_map_file = opts[:mapping]
103
104
  raise MigrationError.new("Migrator missing required field mapping file parameter") if @fld_map_file.nil?
104
105
  @def_file = opts[:defaults]
@@ -145,6 +146,7 @@ module CaRuby
145
146
  end
146
147
  logger.info { "Migration paths:\n#{print_hash.pp_s}" }
147
148
  logger.info { "Migration creatable classes: #{@creatable_classes.qp}." }
149
+ unless @def_hash.empty? then logger.info { "Migration defaults: #{@def_hash.qp}." } end
148
150
 
149
151
  # add shim modifiers
150
152
  load_shims(@shims)
@@ -540,7 +542,7 @@ module CaRuby
540
542
  # collect the class => path => value entries
541
543
  map = LazyHash.new { Hash.new }
542
544
  config.each do |path_s, value|
543
- next if value.blank?
545
+ next if value.nil_or_empty?
544
546
  klass, path = create_attribute_path(path_s)
545
547
  map[klass][path] = value
546
548
  end
@@ -555,7 +557,7 @@ module CaRuby
555
557
  names = path_s.split('.')
556
558
  # if the path starts with a capitalized class name, then resolve the class.
557
559
  # otherwise, the target class is the start of the path.
558
- klass = names.first =~ /^[A-Z]/ ? @target_class.domain_module.const_get(names.shift) : @target_class
560
+ klass = names.first =~ /^[A-Z]/ ? class_for_name(names.shift) : @target_class
559
561
  # there must be at least one attribute
560
562
  if names.empty? then
561
563
  raise MigrationError.new("Attribute entry in migration configuration is not in <class>.<attribute> format: #{value}")
@@ -574,6 +576,13 @@ module CaRuby
574
576
  # important, since the class must be instantiated.
575
577
  [klass, path]
576
578
  end
579
+
580
+ def class_for_name(name)
581
+ # navigate through the scope to the final class
582
+ name.split('::').inject(@target_class.domain_module) do |scope, cnm|
583
+ scope.const_get(cnm)
584
+ end
585
+ end
577
586
 
578
587
  # @return a new class => [paths] hash from the migration fields configuration map
579
588
  def create_class_paths_hash(fld_map, def_map)
@@ -33,6 +33,8 @@ module CaRuby
33
33
  "#{self.class.qp}@#{proxy_object_id}"
34
34
  end
35
35
 
36
+ alias :qp :print_class_and_id
37
+
36
38
  # Sets the default attribute values for this domain object and its dependents. If this Resource
37
39
  # does not have an identifier, then missing attributes are set to the values defined by
38
40
  # {ResourceAttributes#add_attribute_defaults}.
@@ -54,25 +56,22 @@ module CaRuby
54
56
  end
55
57
 
56
58
  # Validates this domain object and its #{ResourceAttributes.unproxied_cascaded_attributes}
57
- # for completeness prior to a database create or update operation.
58
- # The object is valid if it contains a non-nil value for each mandatory property.
59
- # Objects which have already been validated are skipped.
59
+ # for completeness prior to a database create operation.
60
+ # An object without an identifer is valid if it contains a non-nil value for each mandatory property.
61
+ # Objects which have an identifier or have already been validated are skipped.
62
+ #
63
+ # Subclasses should not override this method, but override the private {#local_validate} instead.
60
64
  #
61
65
  # @return [Resource] this domain object
62
- # @raise [ValidationError] if a mandatory attribute value is missing
66
+ # @raise (see #local_validate)
63
67
  def validate
64
- unless @validated then
65
- logger.debug { "Validating #{qp} required attributes #{self.mandatory_attributes.to_a.to_series}..." }
66
- invalid = missing_mandatory_attributes
67
- unless invalid.empty? then
68
- logger.error("Validation of #{qp} unsuccessful - missing #{invalid.join(', ')}:\n#{dump}")
69
- raise ValidationError.new("Required attribute value missing for #{self}: #{invalid.join(', ')}")
70
- end
68
+ if identifier.nil? and not @validated then
69
+ validate_local
70
+ @validated = true
71
71
  end
72
72
  self.class.unproxied_cascaded_attributes.each do |attr|
73
73
  send(attr).enumerate { |dep| dep.validate }
74
74
  end
75
- @validated = true
76
75
  self
77
76
  end
78
77
 
@@ -550,6 +549,20 @@ module CaRuby
550
549
  merge_attributes(self.class.defaults)
551
550
  end
552
551
 
552
+ # Validates that this domain contains a non-nil value for each mandatory property.
553
+ #
554
+ # Subclasses can override this method for additional validation, but should call super first.
555
+ #
556
+ # @raise [ValidationError] if a mandatory attribute value is missing
557
+ def validate_local
558
+ logger.debug { "Validating #{qp} required attributes #{self.mandatory_attributes.to_a.to_series}..." }
559
+ invalid = missing_mandatory_attributes
560
+ unless invalid.empty? then
561
+ logger.error("Validation of #{qp} unsuccessful - missing #{invalid.join(', ')}:\n#{dump}")
562
+ raise ValidationError.new("Required attribute value missing for #{self}: #{invalid.join(', ')}")
563
+ end
564
+ end
565
+
553
566
  # Enumerates the dependents for setting defaults. Subclasses can override if the
554
567
  # dependents must be visited in a certain order.
555
568
  alias :each_defaults_dependent :each_dependent
@@ -574,7 +587,7 @@ module CaRuby
574
587
  def initialize(base, visited=Set.new, &selector)
575
588
  @base = base
576
589
  @visited = visited << base
577
- @selector = selector || Proc.new { |ref| ref.class.java_attributes }
590
+ @selector = selector || Proc.new { |ref| ref.class.printable_attributes }
578
591
  end
579
592
 
580
593
  def pretty_print(q)
@@ -92,6 +92,13 @@ module Enumerable
92
92
  detect { true }
93
93
  end
94
94
 
95
+ # Returns the last enumerated item in this Enumerable, or nil if this Enumerable is empty.
96
+ #
97
+ # This method is functionally equivalent to +to_a.last+ but is more concise and efficient.
98
+ def last
99
+ detect { true }
100
+ end
101
+
95
102
  # Returns the count of items enumerated in this Enumerable.
96
103
  #
97
104
  # This method is functionally equivalent to +to_a.size+ but is more concise and efficient
@@ -1,4 +1,5 @@
1
- require 'generator'
1
+ # JRuby alert - SyncEnumerator moved from generator to REXML in JRuby 1.5
2
+ require 'rexml/document'
2
3
 
3
4
  # A Coordinate is a convenience Array wrapper class with aliased #x, #y and {#z} dimensions.
4
5
  class Coordinate < Array
@@ -58,7 +59,7 @@ class Coordinate < Array
58
59
  return true if equal?(other)
59
60
  raise TypeError.new("Can't compare #{self} with #{other} since it is not a Coordinate") unless Coordinate === other
60
61
  raise ArgumentError.new("Can't compare #{self} with #{other} since it has a different dimension count") unless size == other.size
61
- SyncEnumerator.new(self.reverse, other.reverse).each_with_index do |pair, index|
62
+ REXML::SyncEnumerator.new(self.reverse, other.reverse).each_with_index do |pair, index|
62
63
  dim = pair.first
63
64
  odim = pair.last
64
65
  raise ArgumentError.new("Can't compare #{self} with missing dimension #{index} to #{other}") unless dim
@@ -0,0 +1,28 @@
1
+ class String
2
+ # @return [Integer] the integer equivalent of this roman numeral
3
+ # @raise ArgumentError if this String is not a roman numeral in the range I-X
4
+ def to_arabic
5
+ case self
6
+ when /^(I{0,3})$/ then $1.size
7
+ when /^(I{0,3})(V|X)$/ then ROMAN_UNITS[$2] - $1.size
8
+ when /^(V)(I{0,3})$/ then ROMAN_UNITS[$1] + $2.size
9
+ else raise ArgumentError.new("#{self} is not a roman numeral in the range I-X")
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ ROMAN_UNITS = {'I' => 1, 'V' => 5, 'X' => 10}
16
+ end
17
+
18
+ class Integer
19
+ # @return [String] the roman numeral equivalent of this integer
20
+ def to_roman
21
+ if self < 1 or self > 10 then raise ArgumentError.new("#{self} cannot be converted to a roman numeral in the range I-X")
22
+ elsif self < 4 then 'I' * self
23
+ elsif self < 6 then ('I' * (5 - self)) + 'V'
24
+ elsif self < 9 then 'V' + ('I' * (self - 5))
25
+ else ('I' * (10 - self)) + 'X'
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module CaRuby
2
- VERSION = "1.4.6"
2
+ VERSION = "1.4.7"
3
3
  end
@@ -0,0 +1,18 @@
1
+ $:.unshift 'lib'
2
+
3
+ require "test/unit"
4
+ require 'caruby/util/roman'
5
+
6
+ class RomanTest < Test::Unit::TestCase
7
+ def test_to_arabic
8
+ ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ].each_with_index do |s, n|
9
+ assert_equal(n + 1, s.to_arabic, "Conversion of #{s} incorrect")
10
+ end
11
+ end
12
+
13
+ def test_to_roman
14
+ ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ].each_with_index do |s, n|
15
+ assert_equal(s, (n + 1).to_roman, "Conversion of #{n + 1} incorrect")
16
+ end
17
+ end
18
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: caruby-core
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.4.6
5
+ version: 1.4.7
6
6
  platform: ruby
7
7
  authors:
8
8
  - OHSU
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-02-26 00:00:00 -08:00
13
+ date: 2011-03-04 00:00:00 -08:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -25,7 +25,7 @@ dependencies:
25
25
  type: :runtime
26
26
  version_requirements: *id001
27
27
  - !ruby/object:Gem::Dependency
28
- name: fastercsv
28
+ name: dbd-jdbc
29
29
  prerelease: false
30
30
  requirement: &id002 !ruby/object:Gem::Requirement
31
31
  none: false
@@ -36,7 +36,7 @@ dependencies:
36
36
  type: :runtime
37
37
  version_requirements: *id002
38
38
  - !ruby/object:Gem::Dependency
39
- name: uom
39
+ name: fastercsv
40
40
  prerelease: false
41
41
  requirement: &id003 !ruby/object:Gem::Requirement
42
42
  none: false
@@ -46,6 +46,17 @@ dependencies:
46
46
  version: "0"
47
47
  type: :runtime
48
48
  version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: uom
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :runtime
59
+ version_requirements: *id004
49
60
  description: " caRuby is a JRuby facade for interaction with caBIG applications.\n"
50
61
  email: caruby.org@gmail.com
51
62
  executables: []
@@ -117,6 +128,7 @@ files:
117
128
  - lib/caruby/util/person.rb
118
129
  - lib/caruby/util/pretty_print.rb
119
130
  - lib/caruby/util/properties.rb
131
+ - lib/caruby/util/roman.rb
120
132
  - lib/caruby/util/stopwatch.rb
121
133
  - lib/caruby/util/topological_sync_enumerator.rb
122
134
  - lib/caruby/util/transitive_closure.rb
@@ -151,6 +163,7 @@ files:
151
163
  - test/lib/caruby/util/person_test.rb
152
164
  - test/lib/caruby/util/pretty_print_test.rb
153
165
  - test/lib/caruby/util/properties_test.rb
166
+ - test/lib/caruby/util/roman_test.rb
154
167
  - test/lib/caruby/util/stopwatch_test.rb
155
168
  - test/lib/caruby/util/topological_sync_enumerator_test.rb
156
169
  - test/lib/caruby/util/transitive_closure_test.rb
@@ -188,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
201
  requirements: []
189
202
 
190
203
  rubyforge_project: caruby
191
- rubygems_version: 1.5.2
204
+ rubygems_version: 1.5.3
192
205
  signing_key:
193
206
  specification_version: 3
194
207
  summary: Ruby facade for caBIG applications.