-
Notifications
You must be signed in to change notification settings - Fork 60
Following references (remote and contained)
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:
- Setting
@client
on the top-levelFHIR::Model
that it fetched - 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:
- Updating
FHIR::Client#parse_reply
, which instantiatesFHIR::Model
resources from raw JSON/XML, to setres.client = self
before returning theres
ource. - 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
- Getting Started
- Configuration
-
Searching
- History
- CRUD
- Transactions
- Validation
-
Operations
$everything
$validate