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
eql?(other)
Alias for: ==
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