Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FI-2906 R4B and R5 support #169

Merged
merged 12 commits into from
Oct 18, 2024
2 changes: 1 addition & 1 deletion .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.6', '2.7']
ruby-version: ['3.0', '3.1', '3.2']

steps:
- uses: actions/checkout@v2
Expand Down
8 changes: 1 addition & 7 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
inherit_from: .rubocop_todo.yml

AllCops:
TargetRubyVersion: 2.4
TargetRubyVersion: 3.0
Exclude:
- '*.gemspec'
- 'Gemfile*'
Expand All @@ -10,9 +10,6 @@ AllCops:
- 'lib/tasks/*'
- 'vendor/**/*'

Documentation:
Enabled: false

Metrics:
Enabled: false

Expand All @@ -22,8 +19,5 @@ Style:
Layout:
Enabled: false

Performance/Casecmp:
Enabled: false

Naming:
Enabled: false
14 changes: 1 addition & 13 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-03-22 01:56:17 -0400 using RuboCop version 0.52.1.
# on 2024-10-14 14:56:48 UTC using RuboCop version 1.23.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 4
# Cop supports --auto-correct.
Performance/RegexpMatch:
Exclude:
- 'lib/fhir_client/model/client_reply.rb'

# Offense count: 197
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 210
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ source 'https://rubygems.org'
gemspec

group :test do
gem 'rubocop', '~> 0.52.1', require: false
gem 'rubocop', '~> 1.23.0', require: false
gem 'awesome_print', require: 'ap'
end
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Ruby FHIR client.

Supports:
* FHIR R4, STU3 and DSTU2
* FHIR R5, R4B, R4, STU3 and DSTU2
* JSON and XML
* All CRUD, including version read and history
* Transactions and Batches
Expand Down Expand Up @@ -59,7 +59,7 @@ patient.destroy
## Advanced Usage

### Changing FHIR Versions
The client defaults to `R4` but can be switched to `DSTU2` or `STU3`. It can also attempt to autodetect the FHIR version based on the `metadata` endpoint.
The client defaults to `R4` but can be switched to other versions. It can also attempt to autodetect the FHIR version based on the `metadata` endpoint.

```ruby
# autodetect the FHIR version
Expand All @@ -71,6 +71,10 @@ elsif version == :dstu2
puts 'FHIR Client using DSTU2'
elsif version == :r4
puts 'FHIR Client using R4'
elsif version == :r4b
puts 'FHIR Client using R4B'
elsif
puts 'FHIR Client using R5'
end

# tell the client to use R4
Expand All @@ -79,7 +83,7 @@ client.use_r4
patient = FHIR::Patient.read('example')
patient = client.read(FHIR::Patient, 'example').resource

# tell the client to use STU3 (default)
# tell the client to use STU3
client.use_stu3
# now use the client normally
patient = FHIR::STU3::Patient.read('example')
Expand Down
3 changes: 2 additions & 1 deletion fhir_client.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']

spec.required_ruby_version = '>= 3.0.0'
spec.add_dependency 'activesupport', '>= 3'
spec.add_dependency 'addressable', '>= 2.3'
spec.add_dependency 'fhir_models', '>= 4.2.1'
spec.add_dependency 'fhir_models', '>= 5.0.0'
spec.add_dependency 'fhir_stu3_models', '>= 3.1.1'
spec.add_dependency 'fhir_dstu2_models', '>= 1.1.1'
spec.add_dependency 'nokogiri', '>= 1.10.4'
Expand Down
1 change: 1 addition & 0 deletions lib/fhir_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Dir.glob(File.join(root, 'fhir_client', 'sections', '**', '*.rb')).each do |file|
require file
end
require_relative 'fhir_client/ext/model' #require first so reference and bundle can inherit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we remove multiversion loading from the start, there is an issue with superclass matching for the versioned classes of bundle and reference defined in ext. This fixed it, I think it's because of the versioned model definitions occurring late in model.rb for fhir_client, and the versioned classes not being loaded from fhir_models.

If we load multiversion from the start, this is not an issue as we load the model.rb files in models that contain the versioned classes. I've removed the line with the other reverted changes.

Dir.glob(File.join(root, 'fhir_client', 'ext', '**', '*.rb')).each do |file|
require file
end
Expand Down
68 changes: 55 additions & 13 deletions lib/fhir_client/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ def use_r4
@default_format = versioned_format_class
end

def use_r4b
@fhir_version = :r4b
@default_format = versioned_format_class
end

def use_r5
@fhir_version = :r5
@default_format = versioned_format_class
end

#
# Instructs the client to specify the minimal Prefer Header where applicable
def use_minimal_preference
Expand All @@ -95,6 +105,10 @@ def detect_version
cap = capability_statement
if cap.is_a?(FHIR::CapabilityStatement)
use_r4
elsif cap.is_a?(FHIR::R4B::CapabilityStatement)
use_r4b
elsif cap.is_a?(FHIR::R5::CapabilityStatement)
use_r5
elsif cap.is_a?(FHIR::STU3::CapabilityStatement)
use_stu3
elsif cap.is_a?(FHIR::DSTU2::Conformance)
Expand Down Expand Up @@ -275,20 +289,36 @@ def try_conformance_formats(default_format)
rescue
@cached_capability_statement = nil
end
if @cached_capability_statement.nil? || !@cached_capability_statement.fhirVersion.starts_with?('4')
use_stu3
if @cached_capability_statement.nil? || !@cached_capability_statement.fhirVersion.starts_with?('4.0')
use_r4b
begin
@cached_capability_statement = parse_reply(FHIR::STU3::CapabilityStatement, frmt, reply)
@cached_capability_statement = parse_reply(FHIR::R4B::CapabilityStatement, frmt, reply)
rescue
@cached_capability_statement = nil
end
unless @cached_capability_statement
use_dstu2
if @cached_capability_statement.nil? || !@cached_capability_statement.fhirVersion.starts_with?('4')
use_r5
begin
@cached_capability_statement = parse_reply(FHIR::DSTU2::Conformance, frmt, reply)
@cached_capability_statement = parse_reply(FHIR::R5::CapabilityStatement, frmt, reply)
rescue
@cached_capability_statement = nil
end
if @cached_capability_statement.nil? || !@cached_capability_statement.fhirVersion.starts_with?('5')
use_stu3
begin
@cached_capability_statement = parse_reply(FHIR::STU3::CapabilityStatement, frmt, reply)
rescue
@cached_capability_statement = nil
end
unless @cached_capability_statement
use_dstu2
begin
@cached_capability_statement = parse_reply(FHIR::DSTU2::Conformance, frmt, reply)
rescue
@cached_capability_statement = nil
end
end
end
end
end
if @cached_capability_statement
Expand Down Expand Up @@ -317,23 +347,35 @@ def parse_reply(klass, format, response)
return nil unless [200, 201].include? response.code
res =
begin
if(@fhir_version == :dstu2 || klass&.ancestors&.include?(FHIR::DSTU2::Model))
if(@fhir_version == :dstu2 || klass < FHIR::DSTU2::Model)
if(format.include?('xml'))
FHIR::DSTU2::Xml.from_xml(response.body)
else
FHIR::DSTU2::Json.from_json(response.body)
end
elsif(@fhir_version == :r4 || klass&.ancestors&.include?(FHIR::Model))
elsif(@fhir_version == :stu3 || klass < FHIR::STU3::Model)
if(format.include?('xml'))
FHIR::Xml.from_xml(response.body)
FHIR::STU3::Xml.from_xml(response.body)
else
FHIR::Json.from_json(response.body)
FHIR::STU3::Json.from_json(response.body)
end
elsif(@fhir_version == :r4b || klass < FHIR::R4B::Model)
if(format.include?('xml'))
FHIR::R4B::Xml.from_xml(response.body)
else
FHIR::R4B::Json.from_json(response.body)
end
elsif(@fhir_version == :r5 || klass < FHIR::R5::Model)
if(format.include?('xml'))
FHIR::R5::Xml.from_xml(response.body)
else
FHIR::R5::Json.from_json(response.body)
end
else
if(format.include?('xml'))
FHIR::STU3::Xml.from_xml(response.body)
FHIR::Xml.from_xml(response.body)
else
FHIR::STU3::Json.from_json(response.body)
FHIR::Json.from_json(response.body)
end
end
rescue => e
Expand All @@ -349,7 +391,7 @@ def set_client_on_resource(resource)

resource.client = self
resource.each_element do |element, _, _|
if element.is_a?(Reference) || element.is_a?(STU3::Reference) || element.is_a?(DSTU2::Reference) || element.respond_to?(:resourceType)
if element.is_a?(Reference) || element.is_a?(FHIR::R4B::Reference) || element.is_a?(FHIR::R5::Reference) || element.is_a?(STU3::Reference) || element.is_a?(DSTU2::Reference) || element.respond_to?(:resourceType)
element.client = self
end
end
Expand Down
16 changes: 16 additions & 0 deletions lib/fhir_client/ext/bundle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,20 @@ class Bundle
include FHIR::BundleExtras
end
end
end

module FHIR
module R4B
class Bundle < FHIR::R4B::Model
include FHIR::BundleExtras
end
end
end

module FHIR
module R5
class Bundle < FHIR::R5::Model
include FHIR::BundleExtras
end
end
end
16 changes: 16 additions & 0 deletions lib/fhir_client/ext/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,19 @@ class Model
end
end
end

module FHIR
module R4B
class Model < FHIR::Model
include FHIR::ModelExtras
end
end
end

module FHIR
module R5
class Model < FHIR::Model
include FHIR::ModelExtras
end
end
end
26 changes: 25 additions & 1 deletion lib/fhir_client/ext/reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def resource_class

module FHIR
module STU3
class Reference
class Reference
include FHIR::ReferenceExtras

def resource_class
Expand All @@ -110,3 +110,27 @@ def resource_class
end
end
end

module FHIR
module R4B
class Reference < FHIR::R4B::Model
include FHIR::ReferenceExtras

def resource_class
"FHIR::R4B::#{resource_type}".constantize unless contained?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FHIR::R4B.const_get(resource_type).

end
end
end
end

module FHIR
module R5
class Reference < FHIR::R5::Model
include FHIR::ReferenceExtras

def resource_class
"FHIR::R5::#{resource_type}".constantize unless contained?
end
end
end
end
4 changes: 4 additions & 0 deletions lib/fhir_client/version_management.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ def versioned_resource_class(klass = nil)
FHIR::STU3
when :dstu2
FHIR::DSTU2
when :r4b
defined?(FHIR::R4B) ? FHIR::R4B : FHIR
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes should be reverted, right?

when :r5
defined?(FHIR::R5) ? FHIR::R5 : FHIR
else
FHIR
end
Expand Down
Loading
Loading