class FHIR::STU3::Model
Public Class Methods
new(hash = {})
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 10 def initialize(hash = {}) from_hash(hash) self.class::METADATA.each do |key, value| local_name = key local_name = value['local_name'] if value['local_name'] if value['max'] > 1 && instance_variable_get("@#{local_name}").nil? instance_variable_set("@#{local_name}".to_sym, []) end end end
Public Instance Methods
==(other)
click to toggle source
allow two FHIR
models to be compared for equality
# File lib/fhir_stu3_models/bootstrap/model.rb, line 27 def ==(other) self.class == other.class && to_hash == other.to_hash end
Also aliased as: eql?
attribute_mismatch(left, right, exclude = [])
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 85 def attribute_mismatch(left, right, exclude = []) if left.respond_to?(:mismatch) && right.respond_to?(:mismatch) left.mismatch right, exclude else compare_attribute(left, right, exclude) end end
compare_attribute(left, right, exclude = [])
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 93 def compare_attribute(left, right, exclude = []) if left.respond_to?(:equals?) && right.respond_to?(:equals?) left.equals? right, exclude elsif left.is_a?(Array) && right.is_a?(Array) && (left.length == right.length) result = true (0...(left.length)).each { |i| result &&= compare_attribute(left[i], right[i], exclude) } result else left == right end end
each_element(path = nil) { |value, metadata, child_path| ... }
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 297 def each_element(path = nil, &block) self.class::METADATA.each do |element_name, metadata| local_name = metadata.fetch :local_name, element_name values = [instance_variable_get("@#{local_name}")].flatten.compact next if values.empty? values.each_with_index do |value, i| child_path = if path.nil? element_name else "#{path}.#{element_name}" end child_path += "[#{i}]" if metadata['max'] > 1 yield value, metadata, child_path value.each_element child_path, &block unless FHIR::STU3::PRIMITIVES.include? metadata['type'] end end self end
equals?(other, exclude = [])
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 66 def equals?(other, exclude = []) (self.class::METADATA.keys - exclude).each do |attribute| return false unless compare_attribute(instance_variable_get("@#{attribute}".to_sym), other.instance_variable_get("@#{attribute}".to_sym), exclude) end true end
hash()
click to toggle source
This is necessary for uniq to properly identify two FHIR
models as being identical
# File lib/fhir_stu3_models/bootstrap/model.rb, line 22 def hash to_hash.hash end
method_missing(method, *_args, &_block)
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 32 def method_missing(method, *_args, &_block) if defined?(self.class::MULTIPLE_TYPES) && self.class::MULTIPLE_TYPES[method.to_s] self.class::MULTIPLE_TYPES[method.to_s].each do |type| type[0] = type[0].upcase value = send("#{method}#{type}".to_sym) return value unless value.nil? end return nil elsif !@extension.nil? && !@extension.empty? ext = @extension.select do |x| name = x.url.tr('-', '_').split('/').last anchor = name.split('#').last (method.to_s == name || method.to_s == anchor) end unless ext.first.nil? return ext.first.value.nil? ? ext.first : ext.first.value end elsif !@modifierExtension.nil? && !@modifierExtension.empty? ext = @modifierExtension.select do |x| name = x.url.tr('-', '_').split('/').last anchor = name.split('#').last (method.to_s == name || method.to_s == anchor) end unless ext.first.nil? return ext.first.value.nil? ? ext.first : ext.first.value end end raise NoMethodError.new("undefined method `#{method}' for #{inspect}", method) end
mismatch(other, exclude = [])
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 73 def mismatch(other, exclude = []) misses = [] (self.class::METADATA.keys - exclude).each do |key| these = attribute_mismatch(instance_variable_get("@#{key}".to_sym), other.instance_variable_get("@#{key}".to_sym), exclude) if !these || (these.is_a?(Array) && !these.empty?) misses << "#{self.class}::#{key}" misses.concat these if these.is_a?(Array) end end misses end
primitive?(datatype, value)
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 275 def primitive?(datatype, value) FHIR::STU3.logger.warn("prefer using FHIR::STU3.primitive? Called from #{caller.first}") FHIR::STU3.primitive?(datatype: datatype, value: value) end
to_reference()
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 62 def to_reference FHIR::STU3::Reference.new(reference: "#{self.class.name.split('::').last}/#{id}") end
valid?()
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 105 def valid? validate.empty? end
validate(contained = nil)
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 110 def validate(contained = nil) validate_profile(self.class::METADATA, contained) end
validate_profile(metadata, contained = nil)
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 114 def validate_profile(metadata, contained = nil) contained_here = [instance_variable_get('@contained'.to_sym)].flatten contained_here << contained contained_here = contained_here.flatten.compact errors = {} metadata.each do |field, meta| if meta.is_a?(Array) # this field has been 'sliced' meta.each do |slice| local_name = slice['local_name'] || field value = [instance_variable_get("@#{local_name}".to_sym)].flatten.compact subset = [] # subset is the values associated with just this slice if slice['type'] == 'Extension' subset = if slice['type_profiles'] value.select { |x| slice['type_profiles'].include?(x.url) } else value end else FHIR::STU3.logger.warn 'Validation not supported on slices (except for Extensions)' end validate_field(field, subset, contained_here, slice, errors) end else local_name = meta['local_name'] || field value = [instance_variable_get("@#{local_name}".to_sym)].flatten.compact validate_field(field, value, contained_here, meta, errors) end end # metadata.each # check multiple types multiple_types = begin self.class::MULTIPLE_TYPES rescue {} end multiple_types.each do |prefix, suffixes| present = [] suffixes.each do |suffix| typename = "#{prefix}#{suffix[0].upcase}#{suffix[1..-1]}" # check which multiple data types are actually present, not just errors # actually, this might be allowed depending on cardinality value = instance_variable_get("@#{typename}") present << typename if !value.nil? || (value.is_a?(Array) && !value.empty?) end errors[prefix] = ["#{prefix}[x]: more than one type present."] if present.length > 1 # remove errors for suffixes that are not present next unless present.length == 1 suffixes.each do |suffix| typename = "#{prefix}#{suffix[0].upcase}#{suffix[1..-1]}" errors.delete(typename) unless present.include?(typename) end end errors.keep_if { |_k, v| (v && !v.empty?) } end
Private Instance Methods
check_binding_uri(uri, value)
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 281 def check_binding_uri(uri, value) valid = false if uri == 'http://hl7.org/fhir/ValueSet/content-type' || uri == 'http://www.rfc-editor.org/bcp/bcp13.txt' matches = MIME::Types[value] json_or_xml = value.downcase.include?('xml') || value.downcase.include?('json') known_weird = ['text/cql', 'application/cql+text'].include?(value) valid = json_or_xml || known_weird || (!matches.nil? && !matches.empty?) elsif uri == 'http://hl7.org/fhir/ValueSet/languages' || uri == 'http://tools.ietf.org/html/bcp47' has_region = !(value =~ /-/).nil? valid = !BCP47::Language.identify(value.downcase).nil? && (!has_region || !BCP47::Region.identify(value.upcase).nil?) else FHIR::STU3.logger.warn "Unable to check_binding_uri on unknown ValueSet: #{uri}" end valid end
validate_field(field, value, contained_here, meta, errors)
click to toggle source
—– validate a field —– field: the field name value: an array of values for this field contained_here: all contained resources to be considered meta: the metadata definition for this field (or slice) errors: the ongoing list of errors
# File lib/fhir_stu3_models/bootstrap/model.rb, line 175 def validate_field(field, value, contained_here, meta, errors) errors[field] = [] # check cardinality count = value.length unless count >= meta['min'] && count <= meta['max'] errors[field] << "#{meta['path']}: invalid cardinality. Found #{count} expected #{meta['min']}..#{meta['max']}" end # check datatype datatype = meta['type'] value.each do |v| klassname = v.class.name.gsub('FHIR::STU3::', '') # if the data type is a generic Resource, validate it if datatype == 'Resource' if FHIR::STU3::RESOURCES.include?(klassname) validation = v.validate(contained_here) errors[field] << validation unless validation.empty? else errors[field] << "#{meta['path']}: expected Resource, found #{klassname}" end # if the data type is a Reference, validate it, but also check the # type_profiles metadata. For example, if it should be a Reference(Patient) elsif datatype == 'Reference' if klassname == 'Reference' validation = v.validate(contained_here) errors[field] << validation unless validation.empty? validate_reference_type(v, meta, contained_here, errors[field]) else errors[field] << "#{meta['path']}: expected Reference, found #{klassname}" end # if the data type is a particular resource or complex type or BackBone element within this resource elsif FHIR::STU3::RESOURCES.include?(datatype) || FHIR::STU3::TYPES.include?(datatype) || v.class.name.start_with?(self.class.name) if datatype == klassname validation = v.validate(contained_here) errors[field] << validation unless validation.empty? else errors[field] << "#{meta['path']}: incorrect type. Found #{klassname} expected #{datatype}" end # if the data type is a primitive, test the regular expression (if any) elsif FHIR::STU3::PRIMITIVES.include?(datatype) primitive_meta = FHIR::STU3::PRIMITIVES[datatype] if primitive_meta['regex'] && primitive_meta['type'] != 'number' match = (v =~ Regexp.new(primitive_meta['regex'])) errors[field] << "#{meta['path']}: #{v} does not match #{datatype} regex" if match.nil? else errors[field] << "#{meta['path']}: #{v} is not a valid #{datatype}" unless FHIR::STU3.primitive?(datatype: datatype, value: v) end end # check binding next unless meta['binding'] next unless meta['binding']['strength'] == 'required' the_codes = [v] if meta['type'] == 'Coding' the_codes = [v.code] elsif meta['type'] == 'CodeableConcept' the_codes = v.coding.map(&:code).compact end has_valid_code = false if meta['valid_codes'] meta['valid_codes'].each do |_key, codes| has_valid_code = true unless (codes & the_codes).empty? break if has_valid_code end else the_codes.each do |code| has_valid_code = true if check_binding_uri(meta['binding']['uri'], code) break if has_valid_code end end errors[field] << "#{meta['path']}: invalid codes #{the_codes}" unless has_valid_code end # value.each errors.delete(field) if errors[field].empty? end
validate_reference_type(ref, meta, contained_here, errors)
click to toggle source
# File lib/fhir_stu3_models/bootstrap/model.rb, line 248 def validate_reference_type(ref, meta, contained_here, errors) return unless ref.reference && meta['type_profiles'] return if ref.reference.start_with?('urn:uuid:', 'urn:oid:') matches_one_profile = false meta['type_profiles'].each do |p| basetype = p.split('/').last matches_one_profile = true if ref.reference.include?(basetype) # check profiled resources profile_basetype = FHIR::STU3::Definitions.basetype(p) matches_one_profile = true if profile_basetype && ref.reference.include?(profile_basetype) end matches_one_profile = true if meta['type_profiles'].include?('http://hl7.org/fhir/StructureDefinition/Resource') if !matches_one_profile && ref.reference.start_with?('#') # we need to look at the local contained resources r = contained_here.find { |x| x.id == ref.reference[1..-1] } if !r.nil? meta['type_profiles'].each do |p| p = p.split('/').last matches_one_profile = true if r.resourceType == p end else FHIR::STU3.logger.warn "Unable to resolve reference #{ref.reference}" end end errors << "#{meta['path']}: incorrect Reference type, expected #{meta['type_profiles'].map { |x| x.split('/').last }.join('|')}" unless matches_one_profile end