openehr-rails 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. checksums.yaml +5 -5
  2. data/.devcontainer/Dockerfile +17 -0
  3. data/.devcontainer/devcontainer.json +13 -0
  4. data/.dockerignore +12 -0
  5. data/.github/workflows/ci.yml +75 -0
  6. data/.github/workflows/release.yml +31 -0
  7. data/.gitignore +5 -2
  8. data/.rspec +1 -0
  9. data/.ruby-version +1 -0
  10. data/CHANGELOG.md +77 -0
  11. data/CLAUDE.md +77 -0
  12. data/Guardfile +70 -0
  13. data/LICENSE +194 -0
  14. data/README.md +139 -0
  15. data/Rakefile +7 -0
  16. data/app/controllers/openehr_rails/application_controller.rb +7 -0
  17. data/app/controllers/openehr_rails/ehrs_controller.rb +29 -0
  18. data/app/controllers/openehr_rails/fhir_controller.rb +143 -0
  19. data/app/controllers/openehr_rails/openehr_api/compositions_controller.rb +117 -0
  20. data/app/controllers/openehr_rails/openehr_api/ehrs_controller.rb +82 -0
  21. data/app/controllers/openehr_rails/queries_controller.rb +56 -0
  22. data/app/controllers/openehr_rails/templates_controller.rb +74 -0
  23. data/app/views/layouts/openehr_rails/application.html.erb +39 -0
  24. data/app/views/openehr_rails/ehrs/index.html.erb +27 -0
  25. data/app/views/openehr_rails/ehrs/show.html.erb +55 -0
  26. data/app/views/openehr_rails/queries/show.html.erb +103 -0
  27. data/app/views/openehr_rails/templates/index.html.erb +132 -0
  28. data/config/routes.rb +47 -0
  29. data/demo_assets/demo_seed.rb +91 -0
  30. data/demo_assets/templates/bmi_calculation.opt +1751 -0
  31. data/demo_assets/templates/patient_blood_pressure.opt +1465 -0
  32. data/demo_assets/templates/problem_list.opt +858 -0
  33. data/doc/DEMO_ja.md +459 -0
  34. data/doc/HANDOFF_openehr_aql_create_from_json.md +165 -0
  35. data/doc/HANDOFF_openehr_factory_missing_type.md +193 -0
  36. data/doc/HANDOFF_openehr_opt_parser.md +146 -0
  37. data/docker/demo.Dockerfile +59 -0
  38. data/docker/docker-compose.yml +12 -0
  39. data/gemfiles/rails_7_2.gemfile +5 -0
  40. data/gemfiles/rails_8_0.gemfile +5 -0
  41. data/gemfiles/rails_8_1.gemfile +5 -0
  42. data/lib/generators/openehr/fhir_profile/fhir_profile_generator.rb +38 -0
  43. data/lib/generators/openehr/install/install_generator.rb +54 -8
  44. data/lib/generators/openehr/install/templates/initializers/openehr.rb +22 -0
  45. data/lib/generators/openehr/install/templates/migrations/create_openehr_ehrs.rb +16 -0
  46. data/lib/generators/openehr/install/templates/migrations/create_openehr_rm_storage.rb +86 -0
  47. data/lib/generators/openehr/install/templates/migrations/create_openehr_rm_versioning.rb +28 -0
  48. data/lib/generators/openehr/install/templates/migrations/create_openehr_templates.rb +14 -0
  49. data/lib/generators/openehr/install/templates/models/openehr_template.rb +3 -0
  50. data/lib/generators/openehr/scaffold/scaffold_generator.rb +231 -197
  51. data/lib/generators/openehr/scaffold/templates/controller.rb.erb +78 -0
  52. data/lib/generators/openehr/scaffold/templates/locale.yml.erb +9 -0
  53. data/lib/generators/openehr/scaffold/templates/migration.rb.erb +18 -0
  54. data/lib/generators/openehr/scaffold/templates/model.rb.erb +15 -0
  55. data/lib/generators/openehr/scaffold/templates/request_spec.rb.erb +46 -0
  56. data/lib/generators/openehr/scaffold/templates/views/_form.html.erb +23 -0
  57. data/lib/generators/openehr/scaffold/templates/views/edit.html.erb +6 -0
  58. data/lib/generators/openehr/scaffold/templates/views/index.html.erb +29 -0
  59. data/lib/generators/openehr/scaffold/templates/views/new.html.erb +5 -0
  60. data/lib/generators/openehr/scaffold/templates/views/show.html.erb +11 -0
  61. data/lib/openehr-rails/version.rb +1 -1
  62. data/lib/openehr-rails.rb +3 -3
  63. data/lib/openehr_rails/aql/dataset_adapter.rb +46 -0
  64. data/lib/openehr_rails/aql/errors.rb +19 -0
  65. data/lib/openehr_rails/aql/executor.rb +29 -0
  66. data/lib/openehr_rails/aql/query_validator.rb +122 -0
  67. data/lib/openehr_rails/aql_queryable.rb +50 -0
  68. data/lib/openehr_rails/engine.rb +9 -0
  69. data/lib/openehr_rails/fhir/capability_statement.rb +49 -0
  70. data/lib/openehr_rails/fhir/deserializer.rb +80 -0
  71. data/lib/openehr_rails/fhir/profile_generator.rb +137 -0
  72. data/lib/openehr_rails/fhir/profile_repository.rb +40 -0
  73. data/lib/openehr_rails/fhir/resource_registry.rb +89 -0
  74. data/lib/openehr_rails/fhir/serializer.rb +88 -0
  75. data/lib/openehr_rails/fhir/type_map.rb +61 -0
  76. data/lib/openehr_rails/naming.rb +23 -0
  77. data/lib/openehr_rails/opt/field_extractor.rb +270 -0
  78. data/lib/openehr_rails/opt/parser.rb +52 -0
  79. data/lib/openehr_rails/opt/remote_fetcher.rb +99 -0
  80. data/lib/openehr_rails/opt.rb +9 -0
  81. data/lib/openehr_rails/release_check.rb +134 -0
  82. data/lib/openehr_rails/rm/canonical_serializer.rb +95 -0
  83. data/lib/openehr_rails/rm/composition.rb +49 -0
  84. data/lib/openehr_rails/rm/composition_committer.rb +68 -0
  85. data/lib/openehr_rails/rm/contribution.rb +41 -0
  86. data/lib/openehr_rails/rm/data_value.rb +70 -0
  87. data/lib/openehr_rails/rm/data_values.rb +182 -0
  88. data/lib/openehr_rails/rm/ehr.rb +20 -0
  89. data/lib/openehr_rails/rm/ehr_serializer.rb +52 -0
  90. data/lib/openehr_rails/rm/graph_builder.rb +194 -0
  91. data/lib/openehr_rails/rm/graph_persister.rb +63 -0
  92. data/lib/openehr_rails/rm/node.rb +44 -0
  93. data/lib/openehr_rails/rm/nodes.rb +36 -0
  94. data/lib/openehr_rails/rm/rm_object_builder.rb +217 -0
  95. data/lib/openehr_rails/rm/type_map.rb +69 -0
  96. data/lib/openehr_rails/rm/version.rb +34 -0
  97. data/lib/openehr_rails/rm.rb +46 -0
  98. data/lib/openehr_rails/runtime_scaffolder.rb +73 -0
  99. data/lib/openehr_rails/storable.rb +238 -0
  100. data/lib/openehr_rails/template_importer.rb +45 -0
  101. data/lib/openehr_rails/template_registry.rb +63 -0
  102. data/lib/openehr_rails/template_uploader.rb +68 -0
  103. data/lib/openehr_rails.rb +66 -0
  104. data/lib/tasks/release_check.rake +17 -0
  105. data/openehr-rails.gemspec +26 -12
  106. data/script/build_demo.sh +129 -0
  107. data/script/build_starter.sh +156 -0
  108. data/spec/generators/openehr/fhir_profile/fhir_profile_generator_spec.rb +30 -0
  109. data/spec/generators/openehr/fhir_profile/remote_fhir_profile_generator_spec.rb +43 -0
  110. data/spec/generators/openehr/install/install_generator_spec.rb +41 -3
  111. data/spec/generators/openehr/scaffold/opt_scaffold_generator_spec.rb +127 -0
  112. data/spec/generators/openehr/scaffold/remote_opt_scaffold_generator_spec.rb +52 -0
  113. data/spec/generators/templates/bmi_calculation.opt +1751 -0
  114. data/spec/generators/templates/openEHR-EHR-OBSERVATION.blood_pressure.v1.adl +21 -2912
  115. data/spec/models/openehr_template_spec.rb +114 -0
  116. data/spec/openehr_rails/aql/dataset_adapter_spec.rb +57 -0
  117. data/spec/openehr_rails/aql/executor_spec.rb +53 -0
  118. data/spec/openehr_rails/aql/model_api_spec.rb +28 -0
  119. data/spec/openehr_rails/aql/query_validator_spec.rb +77 -0
  120. data/spec/openehr_rails/fhir/profile_generator_spec.rb +85 -0
  121. data/spec/openehr_rails/fhir/serializer_spec.rb +125 -0
  122. data/spec/openehr_rails/naming_spec.rb +20 -0
  123. data/spec/openehr_rails/opt/field_extractor_concept_slug_spec.rb +56 -0
  124. data/spec/openehr_rails/opt/field_extractor_ordinal_spec.rb +107 -0
  125. data/spec/openehr_rails/opt/field_extractor_section_spec.rb +69 -0
  126. data/spec/openehr_rails/opt/field_extractor_spec.rb +115 -0
  127. data/spec/openehr_rails/opt/remote_fetcher_spec.rb +71 -0
  128. data/spec/openehr_rails/release_check_spec.rb +82 -0
  129. data/spec/openehr_rails/rm/aql_graph_spec.rb +57 -0
  130. data/spec/openehr_rails/rm/canonical_serializer_spec.rb +77 -0
  131. data/spec/openehr_rails/rm/composition_committer_spec.rb +81 -0
  132. data/spec/openehr_rails/rm/composition_spec.rb +64 -0
  133. data/spec/openehr_rails/rm/create_from_json_roundtrip_spec.rb +131 -0
  134. data/spec/openehr_rails/rm/data_value_spec.rb +85 -0
  135. data/spec/openehr_rails/rm/ehr_serializer_spec.rb +37 -0
  136. data/spec/openehr_rails/rm/graph_builder_spec.rb +62 -0
  137. data/spec/openehr_rails/rm/graph_persister_spec.rb +93 -0
  138. data/spec/openehr_rails/rm/node_spec.rb +63 -0
  139. data/spec/openehr_rails/rm/rm_object_builder_spec.rb +76 -0
  140. data/spec/openehr_rails/rm/type_map_spec.rb +34 -0
  141. data/spec/openehr_rails/runtime_scaffolder_spec.rb +49 -0
  142. data/spec/openehr_rails/storable_spec.rb +131 -0
  143. data/spec/openehr_rails/storable_spec_model.rb +18 -0
  144. data/spec/openehr_rails/template_importer_spec.rb +50 -0
  145. data/spec/openehr_rails/template_uploader_spec.rb +50 -0
  146. data/spec/spec_helper.rb +8 -8
  147. data/spec/support/active_record.rb +177 -0
  148. data/spec/templates/bmi_calculation_without_uid.opt +1748 -0
  149. data/spec/templates/sample_blood_pressure.opt +48 -0
  150. data/spec/unit/opt_parser_spec.rb +47 -0
  151. data/spec/unit/require_smoke_spec.rb +25 -0
  152. data/templates/openehr_template.rb +89 -0
  153. metadata +234 -77
  154. data/README.rdoc +0 -33
  155. data/doc/README_FOR_APP +0 -2
  156. data/lib/generators/openehr/assets/assets_generator.rb +0 -28
  157. data/lib/generators/openehr/assets/templates/javascript.js +0 -1
  158. data/lib/generators/openehr/assets/templates/stylesheet.css.scss +0 -3
  159. data/lib/generators/openehr/controller/controller_generator.rb +0 -17
  160. data/lib/generators/openehr/controller/templates/controller.rb +0 -74
  161. data/lib/generators/openehr/helper/helper_generator.rb +0 -17
  162. data/lib/generators/openehr/helper/templates/helper.rb +0 -2
  163. data/lib/generators/openehr/i18n/i18n_generator.rb +0 -49
  164. data/lib/generators/openehr/i18n/templates/i18n.rb +0 -6
  165. data/lib/generators/openehr/i18n/templates/language.yml +0 -15
  166. data/lib/generators/openehr/install/templates/i18n.rb +0 -8
  167. data/lib/generators/openehr/migration/migration_generator.rb +0 -32
  168. data/lib/generators/openehr/migration/templates/archetypes.rb +0 -10
  169. data/lib/generators/openehr/migration/templates/rms.rb +0 -16
  170. data/lib/generators/openehr/model/model_generator.rb +0 -118
  171. data/lib/generators/openehr/model/templates/activemodel.rb +0 -71
  172. data/lib/generators/openehr/model/templates/archetype.rb +0 -3
  173. data/lib/generators/openehr/model/templates/rm.rb +0 -3
  174. data/lib/generators/openehr/scaffold/templates/_form.html.erb +0 -19
  175. data/lib/generators/openehr/scaffold/templates/application.html.erb +0 -14
  176. data/lib/generators/openehr/scaffold/templates/application_controller.rb +0 -3
  177. data/lib/generators/openehr/scaffold/templates/edit.html.erb +0 -6
  178. data/lib/generators/openehr/scaffold/templates/index.html.erb +0 -28
  179. data/lib/generators/openehr/scaffold/templates/inflections.rb +0 -3
  180. data/lib/generators/openehr/scaffold/templates/layout.css.scss +0 -4
  181. data/lib/generators/openehr/scaffold/templates/new.html.erb +0 -5
  182. data/lib/generators/openehr/scaffold/templates/route.rb +0 -2
  183. data/lib/generators/openehr/scaffold/templates/routes.rb +0 -2
  184. data/lib/generators/openehr/scaffold/templates/show.html.erb +0 -6
  185. data/lib/generators/openehr.rb +0 -60
  186. data/lib/rcomponents.rb +0 -48
  187. data/spec/generator_helper.rb +0 -4
  188. data/spec/generators/openehr/archetyped_base_spec.rb +0 -40
  189. data/spec/generators/openehr/assets/assets_generator_spec.rb +0 -37
  190. data/spec/generators/openehr/controller/controller_generator_spec.rb +0 -25
  191. data/spec/generators/openehr/helper/helper_generator_spec.rb +0 -23
  192. data/spec/generators/openehr/i18n/i18n_generator_spec.rb +0 -61
  193. data/spec/generators/openehr/migration/migration_generator_spec.rb +0 -27
  194. data/spec/generators/openehr/model/model_generator_spec.rb +0 -55
  195. data/spec/generators/openehr/scaffold/scaffold_generator_spec.rb +0 -143
  196. data/spec/generators/templates/openEHR-EHR-CLUSTER.mml_name.v1.adl +0 -146
  197. data/spec/rcomponents/relement_spec.rb +0 -21
  198. data/spec/rcomponents/robservation_spec.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5e5b60eff6a86986f88c8373a073e734646cc518
4
- data.tar.gz: 5f6205b202f3ddf6852415e004b36dd119efeb35
2
+ SHA256:
3
+ metadata.gz: 47f141ace236ed8735976a2ea1dcf01d2e05275d9ae82f18448659198f0f6c29
4
+ data.tar.gz: 9f75fe85bf8a55fbf723d3d61e100ae5d270fdc35413e9acd79fe0fd8c9f2f4b
5
5
  SHA512:
6
- metadata.gz: 7a47c5bff6c47c6c83911080f5fa3de44f7d089d4d549bfa7e6168e65f1280c89794d8c306ce87d3d7c8550afcaec67146360a84bb645f2e02a06eb20f662590
7
- data.tar.gz: f052a4e27affe40db869adf8b5aad272186362bfc8becda62808ee15e7b8b7e31e040c06dcfb7256bd93a8e075e489dea52929618f6caefaafc0c20f3aa7aad2
6
+ metadata.gz: 0fe633b32659bc45213734d878c3d1e958fdb1dda8f7c71f93fc299ac6004d78c525f5d44b44b00b5746241d366d1bcdaca1e702c6189720de974e9d300fdf5e
7
+ data.tar.gz: bda15de876019effa3d914cb969748d3912dcd8af682dba34faf8acb08844243e9083aea2fb4c62ad3aa6385c778be82250913d57dbd4d77a2000d016834f909
@@ -0,0 +1,17 @@
1
+ # syntax=docker/dockerfile:1
2
+ #
3
+ # Dev container for openehr-rails gem development (not the demo app --
4
+ # see docker/demo.Dockerfile for that). Builds from the same Ruby base
5
+ # image as CI/demo for consistency.
6
+
7
+ ARG RUBY_VERSION=4.0.6
8
+ FROM docker.io/library/ruby:${RUBY_VERSION}-slim
9
+
10
+ RUN apt-get update -qq && \
11
+ apt-get install --no-install-recommends -y \
12
+ build-essential git libffi-dev libsqlite3-dev libyaml-dev pkg-config curl less vim-tiny && \
13
+ rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
14
+
15
+ RUN gem install bundler rails --no-document
16
+
17
+ WORKDIR /workspaces/openehr-rails
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "openehr-rails (gem development)",
3
+ "build": {
4
+ "dockerfile": "Dockerfile"
5
+ },
6
+ "postCreateCommand": "bundle install",
7
+ "forwardPorts": [3000],
8
+ "customizations": {
9
+ "vscode": {
10
+ "extensions": ["Shopify.ruby-lsp"]
11
+ }
12
+ }
13
+ }
data/.dockerignore ADDED
@@ -0,0 +1,12 @@
1
+ # Keep .git -- openehr-rails.gemspec shells out to `git ls-files` at
2
+ # runtime (Bundler re-evaluates path gemspecs on every boot), so it must
3
+ # survive into the build context and the final image.
4
+
5
+ demo
6
+ coverage
7
+ pkg
8
+ log/*.log
9
+ tmp
10
+ .bundle
11
+ *.lock
12
+ .gem
@@ -0,0 +1,75 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ workflow_call:
8
+
9
+ jobs:
10
+ spec:
11
+ name: spec (ruby ${{ matrix.ruby }}, rails ${{ matrix.rails }})
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ ruby: ['3.2', '3.3', '3.4', '4.0']
17
+ rails: ['7_2', '8_0', '8_1']
18
+ env:
19
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails_${{ matrix.rails }}.gemfile
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: ${{ matrix.ruby }}
25
+ bundler-cache: true
26
+ - name: Run specs
27
+ run: bundle exec rake spec
28
+
29
+ demo-smoke:
30
+ name: demo smoke (openehr:scaffold end-to-end)
31
+ runs-on: ubuntu-latest
32
+ needs: spec
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: '4.0'
38
+ bundler-cache: true
39
+ - name: Install rails CLI
40
+ run: gem install rails --no-document
41
+ - name: Build demo app from OPT templates
42
+ run: FORCE=1 bash script/build_demo.sh
43
+ - name: Run demo app's generated request specs
44
+ working-directory: demo
45
+ run: bundle exec rspec
46
+
47
+ template-smoke:
48
+ name: application template smoke test
49
+ runs-on: ubuntu-latest
50
+ needs: spec
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - uses: ruby/setup-ruby@v1
54
+ with:
55
+ ruby-version: '4.0'
56
+ bundler-cache: true
57
+ - name: Install rails CLI
58
+ run: gem install rails --no-document
59
+ - name: Generate a new app from templates/openehr_template.rb
60
+ env:
61
+ OPENEHR_RAILS_PATH: ${{ github.workspace }}
62
+ OPENEHR_SAMPLES: '1'
63
+ run: |
64
+ rails new /tmp/openehr_template_smoke --database=sqlite3 --skip-test \
65
+ -m "$GITHUB_WORKSPACE/templates/openehr_template.rb"
66
+ - name: Run the generated app's request specs
67
+ working-directory: /tmp/openehr_template_smoke
68
+ run: bundle exec rspec
69
+ - name: Boot check
70
+ working-directory: /tmp/openehr_template_smoke
71
+ run: |
72
+ bin/rails runner '
73
+ raise "no templates registered" unless OpenehrTemplate.count == 3
74
+ puts "boot ok: #{OpenehrTemplate.count} templates registered"
75
+ '
@@ -0,0 +1,31 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ ci:
13
+ uses: ./.github/workflows/ci.yml
14
+
15
+ release:
16
+ name: Release to RubyGems
17
+ needs: ci
18
+ runs-on: ubuntu-latest
19
+ permissions:
20
+ id-token: write # required for RubyGems Trusted Publishing (OIDC)
21
+ contents: read
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: '4.0'
27
+ bundler-cache: true
28
+ - name: release:check (clean tree, sibling-file tracking, gemspec validity)
29
+ run: bundle exec rake release:check
30
+ - name: Release
31
+ uses: rubygems/release-gem@v1
data/.gitignore CHANGED
@@ -1,7 +1,9 @@
1
1
  /.bundle
2
2
 
3
- /app
4
- /config
3
+ # Generated demo Rails app (reproducible via script/build_demo.sh)
4
+ /demo
5
+
6
+ # /app and /config hold the admin engine (controllers, views, routes)
5
7
  /public
6
8
  /vendor
7
9
  /plugin
@@ -26,6 +28,7 @@ pkg
26
28
  #bundler
27
29
  .bundle
28
30
  *.lock
31
+ .gem
29
32
 
30
33
  #
31
34
  # For MacOS:
data/.rspec CHANGED
@@ -1 +1,2 @@
1
+ --require spec_helper
1
2
  --colour
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,77 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.3.0] - 2026-08-13
9
+
10
+ ### Added
11
+ - `rails g openehr:scaffold`/`openehr:fhir_profile` now accept an OPT URL in
12
+ place of a local path, via a new `OpenehrRails::Opt::RemoteFetcher`
13
+ (redirect-following, timeout- and size-bounded HTTP(S) fetch that
14
+ validates the response actually parses as an operational template).
15
+ - Admin UI (`/openehr`) gained a URL-import field (`OpenehrRails::TemplateImporter`),
16
+ the URL counterpart to the existing drag & drop upload.
17
+ - AQL query support: `OpenehrRails::Aql` (dataset adapter, query validator,
18
+ executor) and a `Model.aql(query, params:)` scope on every scaffolded
19
+ model, plus a browser AQL console at `/openehr/query`.
20
+ - openEHR REST API surface at `/openehr/v1`: `EHR`, `EHR_STATUS`,
21
+ `COMPOSITION` (create/read/update/delete with optimistic concurrency),
22
+ and `QUERY` (AQL).
23
+ - Patient timeline UI at `/openehr/ehrs` (per-EHR composition history,
24
+ grouped by date, with version history disclosure).
25
+ - Docker: `docker/demo.Dockerfile` + `docker/docker-compose.yml` run the
26
+ full demo app in a container with no local Ruby/Rails install needed;
27
+ `.devcontainer/` for gem development itself.
28
+ - `templates/openehr_template.rb`: a Rails application template
29
+ (`rails new myehr -m <url>`) that wires up a brand new app with
30
+ openehr-rails end to end, optionally scaffolding the same 3 sample
31
+ templates as the demo. `script/build_starter.sh` wraps it into a
32
+ standalone repo (README + CI) suitable for a GitHub template repository.
33
+ - CI matrix expanded to Ruby 3.2/3.3/3.4/4.0 x Rails 7.2/8.0/8.1
34
+ (`gemfiles/rails_{7_2,8_0,8_1}.gemfile`), plus a demo-smoke job that
35
+ builds the demo app and runs its generated request specs end to end.
36
+ - `LICENSE` file (Apache License 2.0, matching the gemspec's declared
37
+ license -- previously declared but not actually included in the repo).
38
+
39
+ ### Changed
40
+ - Upgraded the `openehr` runtime dependency from `~> 1.3.0` to `~> 2.0`
41
+ (currently resolves to 2.0.2).
42
+ - `gem.license` corrected to the valid SPDX identifier `Apache-2.0`
43
+ (was the non-SPDX string `"Apache 2.0"`).
44
+ - Added `gem.metadata` (`source_code_uri`, `changelog_uri`,
45
+ `bug_tracker_uri`, `rubygems_mfa_required`) and dropped the deprecated
46
+ `gem.test_files` assignment.
47
+
48
+ ### Fixed
49
+ - `OpenehrRails::Rm` was never `require`d by `lib/openehr_rails.rb`,
50
+ silently disabling the entire RM persistence layer (typed node graph,
51
+ AQL, versioning) in any real host application -- it only appeared to
52
+ work in this gem's own test suite because the spec support file
53
+ required it separately. This was the most significant bug found during
54
+ the 2.0 upgrade work.
55
+ - `OpenehrRails::Opt::Parser`'s raw-content-vs-path detection didn't
56
+ account for a leading UTF-8 BOM, so any BOM-prefixed OPT (common in
57
+ real-world exports) misdetected as a file path and crashed. Affected
58
+ every raw-content caller (`TemplateUploader`, `TemplateRegistry`,
59
+ `Fhir::ProfileRepository`, `RemoteFetcher`), not just one code path.
60
+ - `RmObjectBuilder#build_composition` never set `uid:`, so
61
+ `SELECT c/uid/value` always returned nil.
62
+ - `EventContext#setting` used a raw `CODE_PHRASE` where openehr 2.0
63
+ requires a `DV_CODED_TEXT`.
64
+
65
+ ### Removed
66
+ - Legacy ADL-archetype-only generators (`model`/`controller`/`migration`/
67
+ `helper`/`assets`/`i18n`/`template`/`template_model`, and their shared
68
+ `Openehr::Generators::ArchetypedBase`). OPT is now the only supported
69
+ scaffolding input format; `OpenehrTemplate.from_adl_file` (ADL
70
+ *registration*, not scaffolding) is unaffected.
71
+ - The `ckm_client` gem dependency (only used by the removed legacy
72
+ generators).
73
+
74
+ ## [0.2.2] and earlier
75
+
76
+ Predates this changelog. See `git log` for history back to the project's
77
+ first commit in 2012.
data/CLAUDE.md ADDED
@@ -0,0 +1,77 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Principle
6
+
7
+ Follow the TDD framework as advocated by t-wada:
8
+ - 🔴 Red: Write a failing test
9
+ - 🟢 Green: Write the minimal implementation to pass the test
10
+ - 🔵 Refactor: Improve the code while keeping tests green
11
+ - Take small steps
12
+ - Start with fake implementation (hard-coded values)
13
+ - Use triangulation to generalize
14
+ - Direct implementation is OK when the solution is obvious
15
+ - Keep the test list constantly updated
16
+ - Write tests for areas of concern first
17
+ - opt files must not be changed automatically.
18
+
19
+
20
+ ## Project Overview
21
+
22
+ This is `openehr-rails`, a Rails engine gem that turns an openEHR Operational Template (`.opt`, ADL2/XML) into a working Rails resource in one command: `rails generate openehr:scaffold path/to/template.opt --fhir` emits a model, migration, controller, views, i18n locale, and (with `--fhir`) HL7 FHIR R5 `StructureDefinition` profiles. Generated models persist both as typed columns (for forms/search) and as full openEHR RM data (canonical JSON + a typed node graph with immutable-append versioning), and are queryable via a growing AQL surface. A mountable admin engine (`/openehr`) provides template upload/management, runtime scaffolding, and a FHIR R5 facade. Legacy ADL-archetype-only generators (model/controller/migration/helper/assets/i18n/template/template_model, based on `Openehr::Generators::ArchetypedBase`) have been removed — OPT is the only supported input format for scaffolding.
23
+
24
+ ## Key Commands
25
+
26
+ **Testing:**
27
+ - `rake spec` - Run all RSpec tests
28
+ - `rake` - Default task runs specs
29
+ - `bundle exec rspec spec/path/to/specific_spec.rb` - Run specific test file
30
+
31
+ **Development:**
32
+ - `bundle install` - Install dependencies
33
+ - `bundle exec rake` - Run tests via bundler
34
+ - `bundle exec guard` - Run Guard for automated testing (requires guard-rspec)
35
+
36
+ **Gem Development:**
37
+ - `rake build` - Build the gem (via bundler/gem_tasks)
38
+ - `rake install` - Install the gem locally
39
+ - `rake release` - Release the gem
40
+
41
+ **Demo app** (reproducible, git-ignored): `bash script/build_demo.sh` builds a Rails 8 app in `demo/` from the OPTs and seed data in `demo_assets/`, scaffolding three templates (BMI, problem list, blood pressure) with FHIR profiles. See `doc/DEMO_ja.md` for the full walkthrough.
42
+
43
+ ## Architecture & Structure
44
+
45
+ ### Generators (`lib/generators/openehr/`)
46
+
47
+ - `install/` - one-time setup: mounts the engine at `/openehr`, creates the `OpenehrTemplate` registry model and the `openehr_ehrs` / `openehr_rm_*` RM persistence tables.
48
+ - `scaffold/` - the core generator. Takes an `.opt` file (or, per the roadmap, a remote URL), parses it via `OpenehrRails::Opt::Parser`, extracts fields via `OpenehrRails::Opt::FieldExtractor`, and emits model/migration/controller/views/locale/request-spec/routes. `--namespace` and `--fhir` options available.
49
+ - `fhir_profile/` - emits FHIR R5 `StructureDefinition` JSON per OPT entry into `app/fhir/profiles/`.
50
+
51
+ ### Runtime library (`lib/openehr_rails/`)
52
+
53
+ - `opt.rb`, `opt/parser.rb`, `opt/field_extractor.rb` - OPT parsing (subclasses `OpenEHR::Parser::OPTParser` from the `openehr` gem) and flattening of ENTRY/ELEMENT constraints into field hashes (`FIELD_MAP` is the single source of truth tying a generated column to an RM path + data type).
54
+ - `storable.rb` - mixed into scaffolded models (`OpenehrRails::Storable`): on save, builds a canonical openEHR RM Composition from `FIELD_MAP`, caches it as JSON (`rm_composition` column), and persists it into the RM graph.
55
+ - `rm/` - the RM persistence layer: `Composition`/`Node`/`DataValue` (STI, `openehr_rm_*` tables), `GraphBuilder` (canonical JSON → graph rows), `CanonicalSerializer` (graph → canonical JSON, round-trip invariant), `GraphPersister` (immutable-append versioning), `RmObjectBuilder` (graph → real `OpenEHR::RM` objects), `Ehr`/`Contribution`/`Version` (audit trail).
56
+ - `aql_queryable.rb` - `Model.find_by_path` (FIELD_MAP lookup with RM-graph fallback); full AQL query support is planned to build on the same path-to-column resolution (see roadmap in the project's plan history).
57
+ - `template_registry.rb`, `template_uploader.rb`, `runtime_scaffolder.rb` - the `OpenehrTemplate` model (supports both OPT and legacy ADL archetype registration) and the admin engine's upload + in-process `openehr:scaffold` invocation.
58
+ - `fhir/` - FHIR R5 facade: profile generation, canonical-RM ⇄ FHIR serialization, resource registry, capability statement, served at `/openehr/fhir` by `app/controllers/openehr_rails/fhir_controller.rb`.
59
+
60
+ ### Testing Structure
61
+
62
+ - RSpec with `ammeter` for generator specs; in-memory SQLite (`spec/support/active_record.rb`) for RM-layer specs.
63
+ - `spec/generators/openehr/{install,scaffold,fhir_profile}/` - generator specs (only these three generators remain).
64
+ - `spec/openehr_rails/{opt,rm,fhir}/`, `spec/openehr_rails/*_spec.rb` - runtime library specs.
65
+ - `spec/models/openehr_template_spec.rb`, `spec/unit/opt_parser_spec.rb` - registry model and parser specs.
66
+ - OPT fixtures live in `spec/generators/templates/` and `spec/templates/`; do not hand-edit an existing `.opt` fixture (add a new one instead) — **opt files must not be changed automatically.**
67
+
68
+ ## Development Notes
69
+
70
+ - Ruby 3.0+ required; CI matrix covers 3.2/3.3/3.4/4.0, local dev pinned via `.ruby-version` (currently 4.0.6)
71
+ - Rails 7.0+ and Rails 8.x supported (CI matrix: `gemfiles/rails_{7_2,8_0,8_1}.gemfile`; demo app runs on Rails 8.1)
72
+ - URI compatibility workaround implemented for Ruby 3.4
73
+ - `ostruct` is an explicit development dependency — it left Ruby's default gems as of 4.0, and a couple of specs use `OpenStruct` as a lightweight test double
74
+ - Guard setup available for TDD workflow
75
+ - RuboCop Rails linting configured
76
+ - SimpleCov for test coverage
77
+ - `openehr` gem dependency; see the gem's own README/CHANGELOG for OPT-parser and AQL-engine capabilities and known gaps before relying on either.
data/Guardfile ADDED
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # # Rails files
44
+ # rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ # dsl.watch_spec_files_for(rails.app_files)
46
+ # dsl.watch_spec_files_for(rails.views)
47
+
48
+ # watch(rails.controllers) do |m|
49
+ # [
50
+ # rspec.spec.call("routing/#{m[1]}_routing"),
51
+ # rspec.spec.call("controllers/#{m[1]}_controller"),
52
+ # rspec.spec.call("acceptance/#{m[1]}")
53
+ # ]
54
+ # end
55
+
56
+ # # Rails config changes
57
+ # watch(rails.spec_helper) { rspec.spec_dir }
58
+ # watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ # watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # # Capybara features specs
62
+ # watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
63
+ # watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
64
+
65
+ # # Turnip features and steps
66
+ # watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ # watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ # Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ # end
70
+ end
data/LICENSE ADDED
@@ -0,0 +1,194 @@
1
+ Copyright 2012-2026 Shinji KOBAYASHI
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
15
+
16
+ Apache License
17
+ Version 2.0, January 2004
18
+ http://www.apache.org/licenses/
19
+
20
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
21
+
22
+ 1. Definitions.
23
+
24
+ "License" shall mean the terms and conditions for use, reproduction,
25
+ and distribution as defined by Sections 1 through 9 of this document.
26
+
27
+ "Licensor" shall mean the copyright owner or entity authorized by
28
+ the copyright owner that is granting the License.
29
+
30
+ "Legal Entity" shall mean the union of the acting entity and all
31
+ other entities that control, are controlled by, or are under common
32
+ control with that entity. For the purposes of this definition,
33
+ "control" means (i) the power, direct or indirect, to cause the
34
+ direction or management of such entity, whether by contract or
35
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
36
+ outstanding shares, or (iii) beneficial ownership of such entity.
37
+
38
+ "You" (or "Your") shall mean an individual or Legal Entity
39
+ exercising permissions granted by this License.
40
+
41
+ "Source" form shall mean the preferred form for making modifications,
42
+ including but not limited to software source code, documentation
43
+ source, and configuration files.
44
+
45
+ "Object" form shall mean any form resulting from mechanical
46
+ transformation or translation of a Source form, including but
47
+ not limited to compiled object code, generated documentation,
48
+ and conversions to other media types.
49
+
50
+ "Work" shall mean the work of authorship, whether in Source or
51
+ Object form, made available under the License, as indicated by a
52
+ copyright notice that is included in or attached to the work
53
+ (an example is provided in the Appendix below).
54
+
55
+ "Derivative Works" shall mean any work, whether in Source or Object
56
+ form, that is based on (or derived from) the Work and for which the
57
+ editorial revisions, annotations, elaborations, or other modifications
58
+ represent, as a whole, an original work of authorship. For the
59
+ purposes of this License, Derivative Works shall not include works
60
+ that remain separable from, or merely link (or bind by name) to the
61
+ interfaces of, the Work and Derivative Works thereof.
62
+
63
+ "Contribution" shall mean any work of authorship, including the
64
+ original version of the Work and any modifications or additions to
65
+ that Work or Derivative Works thereof, that is intentionally submitted
66
+ to Licensor for inclusion in the Work by the copyright owner or by an
67
+ individual or Legal Entity authorized to submit on behalf of the
68
+ copyright owner. For the purposes of this definition, "submitted"
69
+ means any form of electronic, verbal, or written communication sent
70
+ to the Licensor or its representatives, including but not limited to
71
+ communication on electronic mailing lists, source code control
72
+ systems, and issue tracking systems that are managed by, or on behalf
73
+ of, the Licensor for the purpose of discussing and improving the
74
+ Work, but excluding communication that is conspicuously marked or
75
+ otherwise designated in writing by the copyright owner as
76
+ "Not a Contribution."
77
+
78
+ "Contributor" shall mean Licensor and any individual or Legal Entity
79
+ on behalf of whom a Contribution has been received by Licensor and
80
+ subsequently incorporated within the Work.
81
+
82
+ 2. Grant of Copyright License. Subject to the terms and conditions of
83
+ this License, each Contributor hereby grants to You a perpetual,
84
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
85
+ copyright license to reproduce, prepare Derivative Works of,
86
+ publicly display, publicly perform, sublicense, and distribute the
87
+ Work and such Derivative Works in Source or Object form.
88
+
89
+ 3. Grant of Patent License. Subject to the terms and conditions of
90
+ this License, each Contributor hereby grants to You a perpetual,
91
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
92
+ (except as stated in this section) patent license to make, have
93
+ made, use, offer to sell, sell, import, and otherwise transfer the
94
+ Work, where such license applies only to those patent claims
95
+ licensable by such Contributor that are necessarily infringed by
96
+ their Contribution(s) alone or by combination of their
97
+ Contribution(s) with the Work to which such Contribution(s) was
98
+ submitted. If You institute patent litigation against any entity
99
+ (including a cross-claim or counterclaim in a lawsuit) alleging
100
+ that the Work or a Contribution incorporated within the Work
101
+ constitutes direct or contributory patent infringement, then any
102
+ patent licenses granted to You under this License for that Work
103
+ shall terminate as of the date such litigation is filed.
104
+
105
+ 4. Redistribution. You may reproduce and distribute copies of the Work
106
+ or Derivative Works thereof in any medium, with or without
107
+ modifications, and in Source or Object form, provided that You
108
+ meet the following conditions:
109
+
110
+ (a) You must give any other recipients of the Work or Derivative
111
+ Works a copy of this License; and
112
+
113
+ (b) You must cause any modified files to carry prominent notices
114
+ stating that You changed the files; and
115
+
116
+ (c) You must retain, in the Source form of any Derivative Works
117
+ that You distribute, all copyright, patent, trademark, and
118
+ attribution notices from the Source form of the Work,
119
+ excluding those notices that do not pertain to any part of
120
+ the Derivative Works; and
121
+
122
+ (d) If the Work includes a "NOTICE" text file as part of its
123
+ distribution, then any Derivative Works that You distribute must
124
+ include a readable copy of the attribution notices contained
125
+ within such NOTICE file, excluding those notices that do not
126
+ pertain to any part of the Derivative Works, in at least one
127
+ of the following places: within a NOTICE text file distributed
128
+ as part of the Derivative Works; within the Source form or
129
+ documentation, if provided along with the Derivative Works; or,
130
+ within a display generated by the Derivative Works, if and
131
+ wherever such third-party notices normally appear. The contents
132
+ of the NOTICE file are for informational purposes only and do
133
+ not modify the License. You may add Your own attribution
134
+ notices within Derivative Works that You distribute, alongside
135
+ or as an addendum to the NOTICE text from the Work, provided
136
+ that such additional attribution notices cannot be construed
137
+ as modifying the License.
138
+
139
+ You may add Your own copyright statement to Your modifications and
140
+ may provide additional or different license terms and conditions
141
+ for use, reproduction, or distribution of Your modifications, or
142
+ for any such Derivative Works as a whole, provided Your use,
143
+ reproduction, and distribution of the Work otherwise complies with
144
+ the conditions stated in this License.
145
+
146
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
147
+ any Contribution intentionally submitted for inclusion in the Work
148
+ by You to the Licensor shall be under the terms and conditions of
149
+ this License, without any additional terms or conditions.
150
+ Notwithstanding the above, nothing herein shall supersede or modify
151
+ the terms of any separate license agreement you may have executed
152
+ with Licensor regarding such Contributions.
153
+
154
+ 6. Trademarks. This License does not grant permission to use the trade
155
+ names, trademarks, service marks, or product names of the Licensor,
156
+ except as required for reasonable and customary use in describing
157
+ the origin of the Work and reproducing the content of the NOTICE
158
+ file.
159
+
160
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed
161
+ to in writing, Licensor provides the Work (and each Contributor
162
+ provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES
163
+ OR CONDITIONS OF ANY KIND, either express or implied, including,
164
+ without limitation, any warranties or conditions of TITLE,
165
+ NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR
166
+ PURPOSE. You are solely responsible for determining the
167
+ appropriateness of using or redistributing the Work and assume any
168
+ risks associated with Your exercise of permissions under this
169
+ License.
170
+
171
+ 8. Limitation of Liability. In no event and under no legal theory,
172
+ whether in tort (including negligence), contract, or otherwise,
173
+ unless required by applicable law (such as deliberate and grossly
174
+ negligent acts) or agreed to in writing, shall any Contributor be
175
+ liable to You for damages, including any direct, indirect, special,
176
+ incidental, or consequential damages of any character arising as a
177
+ result of this License or out of the use or inability to use the
178
+ Work (including but not limited to damages for loss of goodwill,
179
+ work stoppage, computer failure or malfunction, or any and all
180
+ other commercial damages or losses), even if such Contributor has
181
+ been advised of the possibility of such damages.
182
+
183
+ 9. Accepting Warranty or Additional Liability. While redistributing
184
+ the Work or Derivative Works thereof, You may choose to offer, and
185
+ charge a fee for, acceptance of support, warranty, indemnity, or
186
+ other liability obligations and/or rights consistent with this
187
+ License. However, in accepting such obligations, You may act only
188
+ on Your own behalf and on Your sole responsibility, not on behalf
189
+ of any other Contributor, and only if You agree to indemnify,
190
+ defend, and hold each Contributor harmless for any liability
191
+ incurred by, or claims asserted against, such Contributor by reason
192
+ of your accepting any such warranty or additional liability.
193
+
194
+ END OF TERMS AND CONDITIONS