Class: Document::Embedded

Inherits:
Object
  • Object
show all
Extended by:
Enumerize
Includes:
ActiveModel::Model, ActiveModel::Validations::Callbacks
Defined in:
lib/document/embedded.rb

Overview

This concern wraps the logic for embedding document in an active record object. The document fields are stored in a jsonb column. The database migration should look like this:

class CreateRenalwareTransplantRecipientWorkups < ActiveRecord::Migration
  def change
    create_table :transplants_recipient_workups do |t|
      t.belongs_to :patient, index: true, foreign_key: true
      t.timestamp :performed_at
      t.jsonb :document
      t.text :notes

      t.timestamps null: false
    end

    add_index :transplants_recipient_workups, :document, using: :gin
  end
end

You then have to create a class for the document under /app/documents and provide a list of the attributes following the Virtus conventions.

The class also includes the ActiveModel::Model module so you can use validations.

Here’s an example:

module Renalware
  module Transplants
    class RecipientWorkupDocument < Document::Embedded
      attribute :hx_tb, Boolean
      attribute :hx_dvt, Boolean
      attribute :pregnancies_no, Integer
      attribute :cervical_result, String
      attribute :cervical_date, Date

      class Consent < Document::Embedded
        attribute :consent, Boolean
        attribute :consent_date, Date

        validates_presence_of :consent_date, if: :consent
      end

      validates_presence_of :cervical_date
    end
  end
end

You then include the Base module in the parent ActiveRecord and provide the document class to use.

For instance:

module Renalware
  module Transplants
    class RecipientWorkup < ApplicationRecord
      include Document::Base
      has_document class_name: "Renalware::Transplants::RecipientWorkupDocument"
    end
  end
end

The document attributes can be localized, but a special convention must be followed due to the namespaces, especially for simple_form. Simply create a file under config/locales and provide the fields localization:

en:
  activemodel:
    attributes:
      renalware/transplants/recipient_workup_document:
        hx_tb: History of TB?
        hx_dvt: History of DVT?
        pregnancies_no: Number of pregnancies
        cervical_result: Cervical smear result
        cervical_date: Cervical smear date
      renalware/transplants/recipient_workup_document/consent:
        consent: Tx consent?
        consent_date: Tx consent date
  simple_form:
    hints:
      transplants_recipient_workup:
        cervical_date: The date and time of the cervical
        document:
          cervical_date: Just a date
    placeholders:
      transplants_recipient_workup:
        document:
          pregnancies_no: "0"

Then in a form, you simply use the form builder fields_for helper:

= f.simple_fields_for :document, f.object.document do |fd|
  = fd.input :hx_tb, as: :boolean
  = fd.input :hx_dvt, as: :boolean
  = fd.input :cervical_date, as: :date
  = fd.simple_fields_for :consent, fd.object.consent do |fdd|
    = fdd.input :consent
    = fdd.input :consent_date, as: :date

Direct Known Subclasses

Renalware::Accesses::AssessmentDocument, Renalware::Accesses::AssessmentDocument::Admin, Renalware::Accesses::AssessmentDocument::Results, Renalware::Events::Biopsy::Document, Renalware::Events::Investigation::Document, Renalware::Events::Swab::Document, Renalware::HD::ProfileDocument, Renalware::HD::ProfileDocument::Anticoagulant, Renalware::HD::ProfileDocument::CareLevel, Renalware::HD::ProfileDocument::Dialysis, Renalware::HD::ProfileDocument::Drugs, Renalware::HD::ProfileDocument::Transport, Renalware::HD::Session::DNA::Document, Renalware::HD::SessionDocument, Renalware::HD::SessionDocument::AvfAvgAssessment, Renalware::HD::SessionDocument::Complications, Renalware::HD::SessionDocument::Dialysis, Renalware::HD::SessionDocument::HDF, Renalware::HD::SessionDocument::Info, Renalware::HD::SessionDocument::Observations, Renalware::LowClearance::ProfileDocument, Renalware::NestedAttribute, Renalware::PD::Assessment::Document, Renalware::PD::TrainingSession::Document, Renalware::PatientDocument, Renalware::PatientDocument::History, Renalware::PatientDocument::Psychosocial, Renalware::PatientDocument::Referral, Renalware::Renal::ProfileDocument, Renalware::Renal::ProfileDocument::Comorbidities, Renalware::Transplants::DonorOperationDocument, Renalware::Transplants::DonorOperationDocument::Complications, Renalware::Transplants::DonorOperationDocument::Outcome, Renalware::Transplants::DonorWorkupDocument, Renalware::Transplants::DonorWorkupDocument::Comorbidities, Renalware::Transplants::DonorWorkupDocument::CreatinineClearance, Renalware::Transplants::DonorWorkupDocument::GlomerularFiltrationRate, Renalware::Transplants::DonorWorkupDocument::ImagingAndScans, Renalware::Transplants::DonorWorkupDocument::Infections, Renalware::Transplants::DonorWorkupDocument::OtherInvestigations, Renalware::Transplants::DonorWorkupDocument::UrineDipsticks, Renalware::Transplants::RecipientFollowupDocument, Renalware::Transplants::RecipientFollowupDocument::CardiovascularComplication, Renalware::Transplants::RecipientOperationDocument, Renalware::Transplants::RecipientOperationDocument::BKVirus, Renalware::Transplants::RecipientOperationDocument::CadavericDonor, Renalware::Transplants::RecipientOperationDocument::Donor, Renalware::Transplants::RecipientOperationDocument::DonorSpecificAntibodies, Renalware::Transplants::RecipientOperationDocument::Recipient, Renalware::Transplants::RecipientWorkupDocument, Renalware::Transplants::RecipientWorkupDocument::BaseConsent, Renalware::Transplants::RecipientWorkupDocument::CervicalSmear, Renalware::Transplants::RecipientWorkupDocument::Education, Renalware::Transplants::RecipientWorkupDocument::Examination, Renalware::Transplants::RecipientWorkupDocument::Historicals, Renalware::Transplants::RecipientWorkupDocument::ObstetricsAndgynaecology, Renalware::Transplants::RecipientWorkupDocument::Scores, Renalware::Transplants::RegistrationDocument, Renalware::Transplants::RegistrationDocument::CRF, Renalware::Transplants::RegistrationDocument::Codes, Renalware::Transplants::RegistrationDocument::Consent, Renalware::Transplants::RegistrationDocument::HLA, Renalware::Transplants::RegistrationDocument::Organs, Renalware::Transplants::RegistrationDocument::Transplant, Renalware::Transplants::RegistrationDocument::UKTransplantCentre, Renalware::Virology::ProfileDocument, Renalware::Virology::Vaccination::Document

Constant Summary collapse

STRIPPABLE_TYPES =
%w(Float Integer).freeze
@@methods_to_ignore =

rubocop:disable Style/ClassVars

[]

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *arguments, &block) ⇒ Object

Don’t raise exception if known missing attribute



186
187
188
# File 'lib/document/embedded.rb', line 186

def method_missing(method_sym, *arguments, &block)
  super unless @@methods_to_ignore.include? method_sym
end

Class Method Details

.attribute(*args) ⇒ Object

Assign a default value to the attributes using a custom type. Set a validation on nested object.

You can specify an enum attribute by passing the ‘enums` options:

attribute :gender, enums: %i(male female)


144
145
146
147
148
149
150
151
152
153
# File 'lib/document/embedded.rb', line 144

def self.attribute(*args)
  attr_options = args.extract_options!
  attr_name, attr_type = *args

  AttributeInitializer
    .determine_initializer(self, attr_name, attr_type, attr_options)
    .call do |name, type, options|
      super(name, type, options)
    end
end

.attributes_listObject

Returns a list of the Virtus attributes in the model



156
157
158
# File 'lib/document/embedded.rb', line 156

def self.attributes_list
  attribute_set.entries.map(&:name)
end

.old_attribute(attribute) ⇒ Object

Flag an old attribute to be ignored when the document is deserialized from the database

class RecipientWorkupDocument < Document::Base
  old_attribute :hx_tb
end


170
171
172
173
# File 'lib/document/embedded.rb', line 170

def self.old_attribute(attribute)
  @@methods_to_ignore << attribute
  @@methods_to_ignore << "#{attribute}=".to_sym
end

.old_attributes(*list) ⇒ Object

Flag a list of old attribtues to be ignored when the document is deserialized from the database

class RecipientWorkupDocument < Document::Base
  old_attributes :hx_tb, :hx_tb_date, :foo_bar
end


181
182
183
# File 'lib/document/embedded.rb', line 181

def self.old_attributes(*list)
  list.each { |item| old_attribute(item) }
end

Instance Method Details

#strip_leading_trailing_whitespace_from_numbersObject



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/document/embedded.rb', line 114

def strip_leading_trailing_whitespace_from_numbers
  attributes.keys.each do |att|
    # Find the type defined in the document definition eg `attribute :weight, Integer``
    # Note that primitive could be a string or class, hence :to_s
    primitive = self.class.attribute_set[att].type.primitive.to_s

    # If the type is in STRIPPABLE_TYPES ie its a numeric type,
    # and it has arrived as a string (which responds to :strip) then
    # ensure there are no leading or trailing spaces, otherwise Virtus cannot
    # coerce it into the correct type. For example Virtus won't corece
    # " 1" into 1 but will coerce "1" into 1 (FYI the Dry::Types gem (the successor to Virtus)
    # rectifies this).
    # Note also that here in this before_validation callback, the act of assignment in
    # `self[att] =` prompts Virtus to re-attempt to coerce the value, which now, if space
    # has prevented it from doing so before, it will do successfully.
    next unless STRIPPABLE_TYPES.include?(primitive)

    if self[att].respond_to?(:strip)
      self[att] = self[att].strip
    end
  end
end