1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
# We use a class instead of a singleton object because we will use
# a cache to avoid copying instancs of ComplianceControl twice as they
# might be reachable from CCSet **and** its descendent CCBlock.
# More generally spoken, we copy a DAG, not a tree.
class ComplianceControlSetCopier
# Naming Convention: As we are in a domain with quite long names we
# abbreviate compliance_control to cc and
# compliance_check to cck iff used as prefixes.
attr_reader :cc_set_id, :referential_id
def copy cc_set_id, referential_id
@cc_set_id = cc_set_id
@referential_id = referential_id
check_organisation_coherence!
copy_dag
end
private
# Workers
# -------
def check_organisation_coherence!
return true if cc_set.organisation_id == referential.organisation_id
raise ArgumentError, "Incoherent organisation of referential"
end
def copy_dag
# Assure cck_set's existance, just in case cc_set is _empty_.
cck_set
make_cck_blocks
make_ccks_from_ccs
end
def make_all_cck_block_children cc_block, cck_block
cc_block
.compliance_controls
.each{ |compliance_control| make_compliance_check(compliance_control, cck_block.id) }
end
def make_cck_block cc_block
cck_block =
cck_set.compliance_check_blocks.create(
name: name_with_refid(cc_block.name))
make_all_cck_block_children cc_block, cck_block
end
def make_cck_blocks
cc_set.compliance_control_blocks.each(&method(:make_cck_block))
end
def make_ccks_from_ccs
cc_set.compliance_controls.each(&method(:make_compliance_check))
end
def make_compliance_check(compliance_control, cck_block_id = nil)
already_there = get_from_cache compliance_control.id
# We do not want to impose traversal order of the DAG, that would
# make the code more resistant to change...
# So we check if we need to update the compliance_check_block_id
# of a control found in the cache.
# N.B. By traversing the indirect descendents from a set first,
# or IOW, traversing the control_blocks before the controls,
# this check could go away and we could return from the
# method in case of a cache hit.
if already_there
# Purely defensive:
if already_there.compliance_check_block_id.nil? && cck_block_id
already_there.update compliance_check_block_id: cck_block_id
end
return
end
make_compliance_check!(compliance_control, cck_block_id)
end
def make_compliance_check!(compliance_control, cck_block_id)
add_to_cache(
compliance_control.id,
cck_set.compliance_checks.create(
criticity: compliance_control.criticity,
name: name_with_refid(compliance_control.name),
code: compliance_control.code,
origin_code: compliance_control.origin_code,
compliance_check_block_id: cck_block_id
))
end
def name_with_refid name
[name, referential.name].join('-')
end
# Lazy Values
# -----------
def cc_set
@__cc_set__ ||= ComplianceControlSet.find(cc_set_id)
end
def cck_set
@__cck_set__ ||= ComplianceCheckSet.create!(
compliance_control_set_id: cc_set_id,
referential_id: referential_id,
workbench_id: referential.workbench_id,
name: name_with_refid(cc_set.name),
status: 'new'
)
end
def referential
@__referential__ ||= Referential.find(referential_id)
end
# Copy Cache
# ----------
def add_to_cache key, obj
# Right now we map key -> obj, in case memory consumption becomes too important
# we can map key -> obj.id and fetch the object from the database in get_from_cache
# (time vs. space tradeoff)
copy_cache.merge!(key => obj)
end
def get_from_cache key
copy_cache[key].tap do | ele |
end
end
def copy_cache
@__copy_cache__ ||= Hash.new
end
end
|