backend/app/lib/merge_helpers.rb
module MergeHelpers
def parse_references(request)
target = JSONModel.parse_reference(request.target['ref'])
victims = request.victims.map {|victim| JSONModel.parse_reference(victim['ref'])}
[target, victims]
end
def check_repository(target, victims, repo_id)
repo_uri = JSONModel(:repository).uri_for(repo_id)
if ([target] + victims).any? {|r| r[:repository] != repo_uri}
raise BadParamsException.new(:merge_request => ["All records to merge must be in the repository specified"])
end
end
def ensure_type(target, victims, type)
if (victims.map {|r| r[:type]} + [target[:type]]).any? {|t| t != type}
raise BadParamsException.new(:merge_request => ["This merge request can only merge #{type} records"])
end
end
def parse_selections(selections, path=[], all_values={})
selections.each_pair do |k, v|
path << k
position = selections['position']
case v
when String
if v === "REPLACE"
all_values.merge!({"#{path.join(".")}.#{position}" => "#{v}"})
path.pop
else
path.pop
next
end
when Hash then parse_selections(v, path, all_values)
when Array then v.each_with_index do |v2, index|
next if v2.is_a? String
path << index
parse_selections(v2, path, all_values)
end
path.pop
else
path.pop
next
end
end
path.pop
return all_values
end
# when merging, set the agent id foreign key (e.g, agent_person_id, agent_family_id...) from the victim to the target
def set_agent_id(target_id, subrecord)
if subrecord['agent_person_id']
subrecord['agent_person_id'] = target_id
elsif subrecord['agent_family_id']
subrecord['agent_family_id'] = target_id
elsif subrecord['agent_corporate_entity_id']
subrecord['agent_corporate_entity_id'] = target_id
elsif subrecord['agent_software_id']
subrecord['agent_software_id'] = target_id
# this section updates related_agents ids
elsif subrecord['agent_person_id_0']
subrecord['agent_person_id_0'] = target_id
elsif subrecord['agent_family_id_0']
subrecord['agent_family_id_0'] = target_id
elsif subrecord['agent_corporate_entity_id_0']
subrecord['agent_corporate_entity_id_0'] = target_id
end
end
def merge_details(target, victim, selections, params)
target[:linked_events] = []
victim[:linked_events] = []
subrec_add_replacements = []
field_replacements = []
victim_values = {}
values_from_params = params[:merge_request_detail].selections
# this code breaks selections into arrays like this:
# ["agent_record_identifiers", 1, "append", 2] // add entire subrec, record is in position 2
# ["agent_record_controls", 0, "replace", 1] // replace entire subrec, record is in position 1
# ["agent_record_controls", 0, "maintenance_status", 1] // replace field, record is in position 1
# ["agent_record_controls", 0, "publication_status", 0] // replace field, record is in position 0
# ["agent_record_controls", 0, "maintenance_agency", 3]
# and then creates data structures for the subrecords to append, replace entirely, and replace by field. record in in position 3
selections.each_key do |key|
path = key.split(".")
path_fix = []
path.each do |part|
if part.length === 1
part = part.to_i
elsif (part.length === 2) and (part.start_with?('1'))
part = part.to_i
end
path_fix.push(part)
end
subrec_name = path_fix[0]
victim_values[subrec_name] = values_from_params[subrec_name]
# subrec level add/replace
if path_fix[2] == "append" || path_fix[2] == "replace"
subrec_add_replacements.push(path_fix)
# field level replace
else
field_replacements.push(path_fix)
end
end
merge_details_subrec(target, victim, subrec_add_replacements, victim_values)
merge_details_replace_field(target, victim, field_replacements, victim_values)
target['title'] = target['names'][0]['sort_name']
target
# This code can be hard to debug when things go wrong, especially since details of problems aren't bubbled up to the frontend where the user is.
# So we'll make sure to catch problems and dump out any info we know.
rescue => e
STDERR.puts "EXCEPTION!"
STDERR.puts e.inspect
STDERR.puts e.backtrace
end
# do field replace operations
def merge_details_replace_field(target, victim, selections, values)
selections.each do |path_fix|
subrec_name = path_fix[0]
# this is the index of the order the user arranged the subrecs in the form, not the order of the subrecords in the DB.
ind = path_fix[1]
field = path_fix[2]
position = path_fix[3]
subrec_index = find_subrec_index_in_victim(victim, subrec_name, position)
target[subrec_name][ind][field] = victim[subrec_name][subrec_index][field]
end
end
# do subrec replace operations
def merge_details_subrec(target, victim, selections, values)
selections.each do |path_fix|
subrec_name = path_fix[0]
# this is the index of the order the user arranged the subrecs in the form, not the order of the subrecords in the DB.
ind = path_fix[1]
mode = path_fix[2]
position = path_fix[3]
subrec_index = find_subrec_index_in_victim(victim, subrec_name, position)
replacer = victim[subrec_name][subrec_index]
# notes are a special case because of the way they store JSON in a db field. So Reordering is not supported, and we can assume the position in the merge request is the position in the victims notes subrecord JSON.
if subrec_name == "notes"
replacer = victim["notes"][ind]
to_append = process_subrecord_for_merge(target, replacer, subrec_name, mode, ind)
target[subrec_name].push(process_subrecord_for_merge(target, replacer, subrec_name, mode, ind))
elsif mode == "replace"
target[subrec_name][ind] = process_subrecord_for_merge(target, replacer, subrec_name, mode, ind)
elsif mode == "append"
target[subrec_name].push(process_subrecord_for_merge(target, replacer, subrec_name, mode, ind))
end
end
end
# we don't know how the user reordered the subrecords on the merge form,
# so find the index with the right data given the position of the right thing to replace/add by searching for it.
def find_subrec_index_in_victim(victim, subrec_name, position)
ind = nil
victim[subrec_name].each_with_index do |subrec, i|
if i == position
ind = i
break
end
end
return ind ? ind : -1
end
# before we can merge a subrecord, we need to update the IDs, tweak things to prevent validation issues, etc
def process_subrecord_for_merge(target, subrecord, jsonmodel_type, mode, ind)
target_id = target['id']
if jsonmodel_type == 'names'
# an agent name can only have one authorized or display name.
# make sure the name being merged in doesn't conflict with this
# if appending, always disable fields that validate across a set. If replacing, always keep values from target
if mode == "append"
subrecord['authorized'] = false
subrecord['is_display_name'] = false
elsif mode == "replace"
subrecord['authorized'] = target['names'][ind]['authorized']
subrecord['is_display_name'] = target['names'][ind]['is_display_name']
end
elsif jsonmodel_type == 'agent_record_identifiers'
# same with agent_record_identifiers being marked as primary, we can only have one
if mode == "append"
subrecord['primary_identifier'] = false
elsif mode == "replace"
subrecord['primary_identifier'] = target['agent_record_identifiers'][ind]['primary_identifier']
end
end
set_agent_id(target_id, subrecord)
return subrecord
end
# NOTE: this code is a duplicate of the auto_generate code for creating sort name
# in the name_person, name_family, name_software, name_corporate_entity models
# Consider refactoring when continued work done on the agents model enhancements
def preview_sort_name(target)
result = ""
case target['jsonmodel_type']
when 'name_person'
if target["name_order"] === "inverted"
result << target["primary_name"] if target["primary_name"]
result << ", #{target["rest_of_name"]}" if target["rest_of_name"]
elsif target["name_order"] === "direct"
result << target["rest_of_name"] if target["rest_of_name"]
result << " #{target["primary_name"]}" if target["primary_name"]
else
result << target["primary_name"] if target["primary_name"]
end
result << ", #{target["prefix"]}" if target["prefix"]
result << ", #{target["suffix"]}" if target["suffix"]
result << ", #{target["title"]}" if target["title"]
result << ", #{target["number"]}" if target["number"]
result << " (#{target["fuller_form"]})" if target["fuller_form"]
result << ", #{target["dates"]}" if target["dates"]
when 'name_corporate_entity'
result << "#{target["primary_name"]}" if target["primary_name"]
result << ". #{target["subordinate_name_1"]}" if target["subordinate_name_1"]
result << ". #{target["subordinate_name_2"]}" if target["subordinate_name_2"]
grouped = [target["number"], target["dates"]].reject {|v| v.nil?}
result << " (#{grouped.join(" : ")})" if not grouped.empty?
when 'name_family'
result << target["family_name"] if target["family_name"]
result << ", #{target["prefix"]}" if target["prefix"]
result << ", #{target["dates"]}" if target["dates"]
when 'name_software'
result << "#{target["manufacturer"]} " if target["manufacturer"]
result << "#{target["software_name"]}" if target["software_name"]
result << " #{target["version"]}" if target["version"]
end
result << " (#{target["qualifier"]})" if target["qualifier"]
result.lstrip!
if result.length > 255
return result[0..254]
else
return result
end
end
end