Skip to content

Commit db9f2f3

Browse files
authored
Merge pull request #5 from apsislabs/2-joined-model-shorthand
#2 joined model shorthand
2 parents 7adec15 + 32bdd2a commit db9f2f3

File tree

10 files changed

+127
-38
lines changed

10 files changed

+127
-38
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
/test/sample/tmp/
1515
*.sqlite3
1616
*.log
17+
.rspec_status

.rspec_status

Lines changed: 0 additions & 14 deletions
This file was deleted.

lib/phi_attrs/phi_record.rb

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ module PhiRecord
77
included do
88
class_attribute :__phi_exclude_methods
99
class_attribute :__phi_include_methods
10+
class_attribute :__phi_extended_methods
1011
class_attribute :__phi_methods_wrapped
1112

13+
after_initialize :wrap_phi
14+
1215
self.__phi_methods_wrapped = []
16+
self.__phi_extended_methods = []
1317
end
1418

1519
class_methods do
@@ -21,6 +25,10 @@ def include_in_phi(*methods)
2125
self.__phi_include_methods = methods.map(&:to_s)
2226
end
2327

28+
def extend_phi_access(*methods)
29+
self.__phi_extended_methods = methods.map(&:to_s)
30+
end
31+
2432
def allow_phi!(user_id, reason)
2533
RequestStore.store[:phi_access] ||= {}
2634

@@ -42,9 +50,7 @@ def disallow_phi!
4250
end
4351
end
4452

45-
def initialize(*args)
46-
super(*args)
47-
53+
def wrap_phi
4854
# Disable PHI access by default
4955
@__phi_access_allowed = false
5056
@__phi_access_logged = false
@@ -54,11 +60,14 @@ def initialize(*args)
5460
end
5561

5662
def __phi_wrapped_methods
57-
attribute_names - self.class.__phi_exclude_methods.to_a + self.class.__phi_include_methods.to_a - [self.class.primary_key]
63+
associations = self.class.reflect_on_all_associations.map(&:name).map(&:to_s)
64+
excluded_methods = self.class.__phi_exclude_methods.to_a
65+
included_methods = self.class.__phi_include_methods.to_a
66+
associations + attribute_names - excluded_methods + included_methods - [self.class.primary_key]
5867
end
5968

6069
def allow_phi!(user_id, reason)
61-
PhiAttrs::Logger.tagged( *phi_log_keys ) do
70+
PhiAttrs::Logger.tagged(*phi_log_keys) do
6271
@__phi_access_allowed = true
6372
@__phi_user_id = user_id
6473
@__phi_access_reason = reason
@@ -107,10 +116,21 @@ def phi_wrap_method(method_name)
107116
raise PhiAttrs::Exceptions::PhiAccessException, "Attempted PHI access for #{self.class.name} #{@__phi_user_id}" unless phi_allowed?
108117

109118
unless @__phi_access_logged
110-
PhiAttrs::Logger.info("'#{phi_allowed_by}' accessing #{self.class.name}.\n\t access logging triggered by method: #{method_name}")
119+
PhiAttrs::Logger.info("'#{phi_allowed_by}' accessing #{self.class.name}. Triggered by method: #{method_name}")
111120
@__phi_access_logged = true
112121
end
113122

123+
# extend PHI access to relationships if needed
124+
if self.class.__phi_extended_methods.include? method_name
125+
126+
# get the unwrapped relation
127+
relation = send(unwrapped_method, *args, &block)
128+
129+
if relation.class.included_modules.include?(PhiRecord)
130+
relation.allow_phi!(phi_allowed_by, phi_access_reason) unless relation.phi_allowed?
131+
end
132+
end
133+
114134
send(unwrapped_method, *args, &block)
115135
end
116136
end

lib/phi_attrs/railtie.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
module PhiAttrs
55
class Railtie < Rails::Railtie
6-
initializer 'rolify.initialize' do
6+
initializer 'phi_attrs.initialize' do
77
ActiveSupport.on_load(:active_record) do
88
ActiveRecord::Base.send :extend, PhiAttrs
99
end

lib/phi_attrs/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module PhiAttrs
2-
VERSION = '0.1.0'.freeze
2+
VERSION = '0.1.2'.freeze
33
end

spec/internal/app/models/address.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Address < ActiveRecord::Base
2+
belongs_to :patient_info
3+
phi_model
4+
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
class PatientDetail < ActiveRecord::Base
2+
belongs_to :patient_info
23
phi_model
34
end

spec/internal/app/models/patient_info.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
class PatientInfo < ActiveRecord::Base
2+
has_one :patient_detail, inverse_of: 'patient_info'
3+
has_one :address, inverse_of: 'patient_info'
4+
25
phi_model
36

7+
extend_phi_access :patient_detail
8+
49
exclude_from_phi :last_name
510
include_in_phi :birthday
611

spec/internal/db/migrate/20170214100255_create_patient_infos.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ def change
88
end
99

1010
create_table :patient_details do |t|
11+
t.belongs_to :patient_info
1112
t.string :detail
1213
t.timestamps
1314
end
15+
16+
create_table :addresses do |t|
17+
t.belongs_to :patient_info
18+
t.string :address
19+
end
1420
end
1521
end

spec/phi_attrs_spec.rb

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,31 +84,64 @@
8484
end
8585

8686
context 'instance authorized' do
87-
it 'allows access to an authorized instance' do
88-
expect { patient_jane.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
87+
context 'single record' do
88+
it 'allows access to an authorized instance' do
89+
expect { patient_jane.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
8990

90-
patient_jane.allow_phi! 'test', 'unit tests'
91+
patient_jane.allow_phi! 'test', 'unit tests'
9192

92-
expect { patient_jane.first_name }.not_to raise_error
93-
end
93+
expect { patient_jane.first_name }.not_to raise_error
94+
end
9495

95-
it 'only allows access to the authorized instance' do
96-
patient_jane.allow_phi! 'test', 'unit tests'
96+
it 'only allows access to the authorized instance' do
97+
patient_jane.allow_phi! 'test', 'unit tests'
9798

98-
expect { patient_jane.first_name }.not_to raise_error
99-
expect { patient_john.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
100-
end
99+
expect { patient_jane.first_name }.not_to raise_error
100+
expect { patient_john.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
101+
end
101102

102-
it 'revokes access after calling disallow_phi!' do
103-
expect { patient_jane.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
103+
it 'revokes access after calling disallow_phi!' do
104+
expect { patient_jane.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
104105

105-
patient_jane.allow_phi! 'test', 'unit tests'
106+
patient_jane.allow_phi! 'test', 'unit tests'
106107

107-
expect { patient_jane.first_name }.not_to raise_error
108+
expect { patient_jane.first_name }.not_to raise_error
108109

109-
patient_jane.disallow_phi!
110+
patient_jane.disallow_phi!
110111

111-
expect { patient_jane.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
112+
expect { patient_jane.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
113+
end
114+
115+
it 'allows access on an instance that already exists' do
116+
john = PatientInfo.create(first_name: 'John', last_name: 'Doe')
117+
expect { john.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
118+
119+
john_id = john.id
120+
john = nil
121+
122+
john = PatientInfo.find(john_id)
123+
expect { john.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
124+
125+
john.allow_phi! 'test', 'unit tests'
126+
expect { john.first_name }.not_to raise_error
127+
expect(john.first_name).to eq 'John'
128+
end
129+
end
130+
131+
context 'collection' do
132+
it 'allows access when fetched as a collection' do
133+
jay = PatientInfo.create(first_name: "Jay")
134+
bob = PatientInfo.create(first_name: "Bob")
135+
moe = PatientInfo.create(first_name: "Moe")
136+
137+
patients = PatientInfo.all
138+
139+
expect(patients).to contain_exactly(jay, bob, moe)
140+
expect { patients.map(&:first_name) }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
141+
142+
patients.map { |p| p.allow_phi! 'test', 'unit tests' }
143+
expect { patients.map(&:first_name) }.not_to raise_error
144+
end
112145
end
113146
end
114147

@@ -138,4 +171,37 @@
138171
expect { patient_jane.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
139172
end
140173
end
174+
175+
context 'extended authorization' do
176+
let(:mary_detail) { PatientDetail.create(detail: 'Lorem Ipsum') }
177+
let(:mary_address) { Address.create(address: '123 Street Ave') }
178+
let(:patient_mary) { PatientInfo.create(first_name: 'Mary', last_name: 'Jay', address: mary_address, patient_detail: mary_detail) }
179+
180+
it 'extends access to extended association' do
181+
expect { patient_mary.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
182+
expect { patient_mary.patient_detail }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
183+
expect { patient_mary.patient_detail.detail }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
184+
185+
patient_mary.allow_phi! 'test', 'unit tests'
186+
187+
expect { patient_mary.first_name }.not_to raise_error
188+
expect { patient_mary.patient_detail.detail }.not_to raise_error
189+
expect(patient_mary.patient_detail.detail).to eq 'Lorem Ipsum'
190+
end
191+
192+
it 'does not extend to unextended association' do
193+
expect { patient_mary.first_name }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
194+
expect { patient_mary.address }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
195+
expect { patient_mary.address.address }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
196+
197+
patient_mary.allow_phi! 'test', 'unit tests'
198+
expect { patient_mary.first_name }.not_to raise_error
199+
expect { patient_mary.address }.not_to raise_error
200+
expect { patient_mary.address.address }.to raise_error(PhiAttrs::Exceptions::PhiAccessException)
201+
202+
patient_mary.address.allow_phi! 'test', 'unit test'
203+
expect { patient_mary.address.address }.not_to raise_error
204+
expect(patient_mary.address.address).to eq '123 Street Ave'
205+
end
206+
end
141207
end

0 commit comments

Comments
 (0)