Skip to content

Following references (remote and contained)

Andrew Ross edited this page Jun 13, 2016 · 6 revisions

Following references

In the application we're building with fhir_client, we have to follow a lot of references. So far, they have mostly been remote references, so we've made our lives easier by defining FHIR::Reference#read to work for them:

module FHIR
  class Reference
    def read
      type, id = reference.split("/")
      klass = "FHIR::#{type}".constantize
      klass.read(client, id)
    end
  end
end

This required having access to client, which we were able to gain (slightly hackily) by:

  1. Setting @client on the top-level FHIR::Model that it fetched
  2. Recursively setting @client on all of that model's child resources by iterating through its instance variables

The code we wrote to do this was:

  1. Updating FHIR::Client#parse_reply, which instantiates FHIR::Model resources from raw JSON/XML, to set res.client = self before returning the resource.
  2. Doing this:
module FHIR
  class Model
    def client=(client)
      @client = client

      # Ensure the client-setting cascades to all child models
      instance_values.each do |_variable_name, values|
        Array.wrap(values).each do |value|
          next unless value.is_a?(FHIR::Model)
          next if value.client == client
          value.client = client
        end
      end
    end
  end
end

So this worked well, and allowed us to do things like:

medication = medicationOrder.medicationReference.read
practitioner = patient.careProvider.first.read

But, then we realized we needed to also be able to access contained resources. For that, we need to be able to access either the top-level resource or at least its contained attribute from any of its FHIR::Reference children.

But I'm not sure what the best way of doing this is, and want to solicit advice.

I've been looking at the documentation, and it seems like contained resources are only present on DomainResource elements, which every resource in the list extends except for Bundle, Parameters, and Binary -- which actually points to a slight bug in fhir_models, since currently Bundle responds to contained even though it shouldn't.

So if we could freely move up and down the model tree, it seems like we would want to find the nearest DomainResource above the FHIR::Reference and look in its contained attribute for matches.

If we just extended what we've already done, it might look like this:

module FHIR
  class Reference
    def contained?
      reference.to_s.start_with?("#")
    end

    def read
      if contained?
        nearest_domain_resource.contained.detect { |model| model.id == reference[1:] }
      else
        type, id = reference.split("/")
        klass = "FHIR::#{type}".constantize
        klass.read(id, client)
      end
    end
  end
end

However, before we can implement nearest_domain_resource, we need to be able to traverse up and down a tree of models, and we also need to be able, ideally, to check if a resource is a domain resource (or even is_a?(FHIR::DomainResource), assuming we implement that type of inheritance). And it feels like the proper place for some of these changes might actually be fhir_models.

Any thoughts?

===

Another option would be to call a method on the domain resource itself:

medication = medicationOrder.resolve(medicationOrder.medicationReference)
practitioner = patient.resolve(patient.careProvider.first) 

Maybe resolve is a better name for the method regardless of where it's defined.

I also tried a hybrid approach allowing either the above syntax or a hacky string-based one:

module FHIR
  class Model
    def resolve(reference)
      if reference.is_a?(String)
        reference = reference.split('.').inject(self) { |obj, m| obj.send(m) }
      end

      if reference.contained?
        contained.detect { |resource| resource.id == reference.id }
      else
        reference.klass.read(reference.id)
      end
    end
  end
end

Which led to code like:

namespace :fhir do
  desc "Load some FHIR data"
  task load: :environment do
    FHIR::Model.client = FHIR::Client.new(ENV['FHIR_SERVER_URL'])

    patient = FHIR::Patient.read(ENV['FHIR_PATIENT_ID'])
    observations = FHIR::Observation.search(patient: patient.id)
    conditions = FHIR::Condition.search(patient: patient.id)

    # string syntax, no repetition of `patient`
    practitioner = patient.resolve('careProvider.first')

    # full-reference syntax
    care_plan = FHIR::CarePlan.search(subject: patient.id).first
    contained_activities, external_activities = care_plan.activities.partition { |a| a.reference.contained? }
    contained_resource = care_plan.resolve(contained_activities.first.reference)
    external_resource = care_plan.resolve(external_activities.first.reference)

    binding.pry
  end
end
Clone this wiki locally