Module: PhiAttrs::PhiRecord

Extended by:
ActiveSupport::Concern
Defined in:
lib/phi_attrs/phi_record.rb

Overview

Module for extending ActiveRecord models to handle PHI access logging and restrict access to attributes.

Author:

  • Apsis Labs

Since:

  • 0.1.0

Instance Method Summary collapse

Instance Method Details

#__phi_extended_methodsArray<String>

Get all method names to be wrapped with PHI access extension

Returns:

  • (Array<String>)

    the method names to be wrapped with PHI access extension

Since:

  • 0.1.0



318
319
320
# File 'lib/phi_attrs/phi_record.rb', line 318

def __phi_extended_methods
  self.class.__phi_extend_methods.to_a
end

#__phi_wrapped_methodsArray<String>

Get all method names to be wrapped with PHI access logging

Returns:

  • (Array<String>)

    the method names to be wrapped with PHI access logging

Since:

  • 0.1.0



307
308
309
310
311
312
# File 'lib/phi_attrs/phi_record.rb', line 307

def __phi_wrapped_methods
  excluded_methods = self.class.__phi_exclude_methods.to_a
  included_methods = self.class.__phi_include_methods.to_a

  attribute_names - excluded_methods + included_methods - [self.class.primary_key]
end

#allow_phi(user_id = nil, reason = nil) { ... } ⇒ Object

Enable PHI access for a single instance of this class inside the block. Nested calls to allow_phi will log once per nested call

Examples:

foo = Foo.find(1)
foo.allow_phi('[email protected]', 'viewing patient record') do
 # PHI Access Allowed Here
end
# PHI Access Disallowed Here

Parameters:

  • user_id (String) (defaults to: nil)

    A unique identifier for the person accessing the PHI

  • reason (String) (defaults to: nil)

    The reason for accessing PHI

Yields:

  • The block in which phi access is allowed

Since:

  • 0.1.0



363
364
365
366
# File 'lib/phi_attrs/phi_record.rb', line 363

def allow_phi(user_id = nil, reason = nil, &block)
  get_phi(user_id, reason, &block)
  return
end

#allow_phi!(user_id = nil, reason = nil) ⇒ Object

Enable PHI access for a single instance of this class.

Examples:

foo = Foo.find(1)
foo.allow_phi!('[email protected]', 'viewing patient record')

Parameters:

  • user_id (String) (defaults to: nil)

    A unique identifier for the person accessing the PHI

  • reason (String) (defaults to: nil)

    The reason for accessing PHI

Raises:

  • (ArgumentError)

Since:

  • 0.1.0



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/phi_attrs/phi_record.rb', line 331

def allow_phi!(user_id = nil, reason = nil)
  raise ArgumentError, 'block not allowed. use allow_phi with block' if block_given?

  user_id ||= self.class.current_user
  reason ||= self.class.i18n_reason
  raise ArgumentError, 'user_id and reason cannot be blank' if user_id.blank? || reason.blank?

  PhiAttrs::Logger.tagged(*phi_log_keys) do
    @__phi_access_stack.push({
                               phi_access_allowed: true,
                               user_id: user_id,
                               reason: reason
                             })

    PhiAttrs::Logger.info("PHI Access Enabled for '#{user_id}': #{reason}")
  end
end

#disallow_last_phi!(preserve_extensions: false) ⇒ Object

Revoke last PHI access for a single instance of this class.

Examples:

foo = Foo.find(1)
foo.disallow_last_phi!

Raises:

  • (ArgumentError)

Since:

  • 0.1.0



451
452
453
454
455
456
457
458
459
460
461
# File 'lib/phi_attrs/phi_record.rb', line 451

def disallow_last_phi!(preserve_extensions: false)
  raise ArgumentError, 'block not allowed' if block_given?

  PhiAttrs::Logger.tagged(*phi_log_keys) do
    removed_access = @__phi_access_stack.pop

    revoke_extended_phi! unless preserve_extensions
    message = removed_access.present? ? "PHI access disabled for #{removed_access[:user_id]}" : 'PHI access disabled. No instance level access was granted.'
    PhiAttrs::Logger.info(message)
  end
end

#disallow_phi { ... } ⇒ Object

Dissables PHI access for a single instance of this class inside the block. Nested calls to allow_phi will log once per nested call

Examples:

foo = Foo.find(1)
foo.allow_phi('[email protected]', 'viewing patient record') do
 # PHI Access Allowed Here
end
# PHI Access Disallowed Here

Parameters:

  • user_id (String)

    A unique identifier for the person accessing the PHI

  • reason (String)

    The reason for accessing PHI

Yields:

  • The block in which phi access is allowed

Raises:

  • (ArgumentError)

Since:

  • 0.1.0



433
434
435
436
437
438
439
440
441
442
443
# File 'lib/phi_attrs/phi_record.rb', line 433

def disallow_phi
  raise ArgumentError, 'block required. use disallow_phi! without block' unless block_given?

  add_disallow_flag!
  add_disallow_flag_to_extended_phi!

  yield if block_given?

  remove_disallow_flag_from_extended_phi!
  remove_disallow_flag!
end

#disallow_phi!Object

Revoke all PHI access for a single instance of this class.

Examples:

foo = Foo.find(1)
foo.disallow_phi!

Raises:

  • (ArgumentError)

Since:

  • 0.1.0



405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/phi_attrs/phi_record.rb', line 405

def disallow_phi!
  raise ArgumentError, 'block not allowed. use disallow_phi with block' if block_given?

  PhiAttrs::Logger.tagged(*phi_log_keys) do
    removed_access_for = self.class.__user_id_string(@__phi_access_stack)

    revoke_extended_phi!
    @__phi_access_stack = []

    message = removed_access_for.present? ? "PHI access disabled for #{removed_access_for}" : 'PHI access disabled. No instance level access was granted.'
    PhiAttrs::Logger.info(message)
  end
end

#get_phi(user_id = nil, reason = nil) { ... } ⇒ Object

Enable PHI access for a single instance of this class inside the block. Returns whatever is returned from the block. Nested calls to get_phi will log once per nested call s

Examples:

foo = Foo.find(1)
phi_data = foo.get_phi('[email protected]', 'viewing patient record') do
 foo.phi_field
end

Parameters:

  • user_id (String) (defaults to: nil)

    A unique identifier for the person accessing the PHI

  • reason (String) (defaults to: nil)

    The reason for accessing PHI

Yields:

  • The block in which phi access is allowed

Returns:

  • PHI

Raises:

  • (ArgumentError)

Since:

  • 0.1.0



384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/phi_attrs/phi_record.rb', line 384

def get_phi(user_id = nil, reason = nil)
  raise ArgumentError, 'block required' unless block_given?

  extended_instances = @__phi_relations_extended.clone
  allow_phi!(user_id, reason)

  result = yield if block_given?

  new_extensions = @__phi_relations_extended - extended_instances
  disallow_last_phi!(preserve_extensions: true)
  revoke_extended_phi!(new_extensions) if new_extensions.any?

  result
end

#phi_access_reasonString

The access reason for allowing access to this instance. This is what was passed in when PhiRecord#allow_phi! was called.

Returns:

  • (String)

    the reason passed in to allow_phi!

Since:

  • 0.1.0



477
478
479
# File 'lib/phi_attrs/phi_record.rb', line 477

def phi_access_reason
  phi_context[:reason]
end

#phi_allowed?Boolean

Whether PHI access is allowed for a single instance of this class

Examples:

foo = Foo.find(1)
foo.phi_allowed?

Returns:

  • (Boolean)

    whether PHI access is allowed for this instance

Since:

  • 0.1.0



489
490
491
# File 'lib/phi_attrs/phi_record.rb', line 489

def phi_allowed?
  !phi_context.nil? && phi_context[:phi_access_allowed]
end

#phi_allowed_byString

The unique identifier for whom access has been allowed on this instance. This is what was passed in when PhiRecord#allow_phi! was called.

Returns:

  • (String)

    the user_id passed in to allow_phi!

Since:

  • 0.1.0



468
469
470
# File 'lib/phi_attrs/phi_record.rb', line 468

def phi_allowed_by
  phi_context[:user_id]
end

#reloadObject

Since:

  • 0.1.0



505
506
507
508
# File 'lib/phi_attrs/phi_record.rb', line 505

def reload
  @__phi_relations_extended.clear
  super
end

#require_phi!Object

Require phi access. Raises an error pre-emptively if it has not been granted.

Examples:

def use_phi(patient_record)
  patient_record.require_phi!
  # ...use PHI Freely
end

Raises:

  • (PhiAccessException)

Since:

  • 0.1.0



501
502
503
# File 'lib/phi_attrs/phi_record.rb', line 501

def require_phi!
  raise PhiAccessException, 'PHI Access required, please call allow_phi or allow_phi! first' unless phi_allowed?
end