Skip to content

Commit

Permalink
Restructure mapping export
Browse files Browse the repository at this point in the history
  • Loading branch information
excelsior committed Mar 18, 2023
1 parent 254b184 commit d7069f8
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 71 deletions.
4 changes: 4 additions & 0 deletions app/models/mapping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class Mapping < ApplicationRecord
has_one :mapping_predicates, through: :configuration_profile

has_many :alignments

has_many :predicates, through: :alignments

has_many :terms, source: :mapped_terms, through: :alignments
###
# @description: The selected terms from the original uploaded specification. The user can select one
# ore more terms from it.
Expand Down
323 changes: 252 additions & 71 deletions app/services/exporters/mapping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,81 @@ class Mapping
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"sdo": "http://schema.org/",
"xsd": "http://www.w3.org/2001/XMLSchema#"
"xsd": "http://www.w3.org/2001/XMLSchema#",
"dct:title": {
"@container": "@language"
},
"dct:description": {
"@container": "@language"
},
"dct:created": {
"@type": "http://www.w3.org/2001/XMLSchema#date"
},
"dct:dateModified": {
"@type": "http://www.w3.org/2001/XMLSchema#date"
},
"desm:abstractClassType": {
"@type": "@id"
},
"desm:hasClassMapping": {
"@type": "@id"
},
"desm:hasDSO": {
"@type": "@id"
},
"desm:abstractClassModeled": {
"@type": "@id"
},
"dct:hasPart": {
"@type": "@id"
},
"desm:spineTerm": {
"@type": "@id"
},
"desm:mappedTerm": {
"@type": "@id"
},
"desm:mappingPredicate": {
"@type": "@id"
},
"rdfs:label": {
"@container": "@language"
},
"rdfs:comment": {
"@container": "@language"
},
"rdfs:domain": {
"@type": "@id"
},
"rdfs:subPropertyOf": {
"@type": "@id"
},
"desm:inSchema": {
"@type": "@id"
}
}.freeze

attr_reader :alignments, :created, :mapping, :updated

delegate :configuration_profile, :organization, :specification, :spine, :title,
to: :mapping

delegate :domain, to: :spine

delegate :user, to: :specification

###
# @description: Initializes this class with the instance to export.
###
def initialize instance
@instance = instance
def initialize mapping
@alignments = mapping
.alignments
.where.not(predicate_id: nil)
.includes(:mapped_terms, :predicate, :spine_term)

@created = mapping.created_at.to_date
@mapping = mapping
@updated = mapping.updated_at.to_date
end

###
Expand All @@ -39,101 +106,215 @@ def export
{
"@context": CONTEXT,
"@graph": [
main_node,
*term_nodes.compact
config_node,
class_node,
domain_set_node,
predicate_set_node,
organization_node,
user_node,
domain_node,
*alignment_nodes,
*term_nodes,
*predicates_node
]
}
end

###
# @description: Specifies the format the main node (the node that represents the mapping itself)
# should have.
###
def main_node
private

def alignment_nodes
@alignment_nodes ||= alignments.map {|alignment|
build_alignment_node(alignment)
}
end

# rubocop:disable Metrics/AbcSize
def build_alignment_node(alignment)
mapped_term = alignment.mapped_terms.first
spine_term = alignment.spine_term

node = {
"@id": build_uri("#{domain.slug}/termMapping#{alignment.id}"),
"@type": "desm:TermMapping",
"dct:dateModified": alignment.updated_at.to_date,
"dct:created": alignment.created_at.to_date
}

node["dct:description"] = alignment.comment if alignment.comment?

node.merge(
"desm:spineTerm": build_uri("#{domain.slug}/spine/#{spine_term.id}"),
"desm:mappedTerm": (build_uri("terms/#{mapped_term.id}") if mapped_term),
"desm:mappingPredicate": alignment.predicate.source_uri
)
end
# rubocop:enable Metrics/AbcSize

def build_predicate_node(predicate)
node = {
"@id": predicate.source_uri,
"@type": "skos:Concept",
"skos:prefLabel": predicate.pref_label
}

node["skos:definition"] = predicate.definition if predicate.definition?
node.merge("skos:inScheme": predicate_set_node.fetch(:"@id"))
end

def build_term_node(term, spine: false)
raw = term.raw

node = {
"@id": build_uri(spine ? "#{domain.slug}/spine/#{term.id}" : "terms/#{term.id}"),
"@type": "rdf:Property",
"rdfs:label": raw.fetch("rdfs:label"),
**raw.slice("rdfs:label", "rdfs:comment"),
"rdfs:domain": domain.source_uri
}

node["rdfs:subPropertyOf"] = term.source_uri unless spine
node.merge("desm:inSchema": spec_node_id)
end

def build_uri(value)
URI(Desm::APP_DOMAIN) + "#{configuration_profile.slug}-#{configuration_profile.id}/#{value}"
end

def class_node
{
"@id": "http://desmsolutions.org/TermMapping/#{@instance.id}",
"@id": class_node_id,
"@type": "desm:AbstractClassMapping",
"dcterms:created": @instance.created_at.strftime("%F"),
"dcterms:dateModified": @instance.updated_at.strftime("%F"),
"dcterms:title": @instance.title,
# @todo: Where to take this from
"dcterms:description": "",
"desm:abstractClassMapped": {"@id": @instance.specification.domain.uri},
"dcterms:hasPart": @instance.alignments.map {|alignment|
{"@id": alignment.uri}
}
"dct:title": "\"#{title}\" class mapping",
"dct:description": "A partial class mapping.",
"dct:created": created,
"dct:dateModified": updated,
"desm:isClassMappingOf": build_uri("mappingConfig"),
"desm:abstractClassModeled": domain.source_uri,
"dct:hasPart": alignment_nodes.map {|node| node.fetch(:"@id") }
}
end

###
# @description: For each alignment to a spine term, we build basically 3 nodes, one ofr the
# alignment, one for the mapped property (more than one if there are many), and the last one
# for the spine property.
###
def term_nodes
alignments = @instance
.alignments
.includes(:predicate, mapped_terms: :property, spine_term: :property)

alignments.map do |alignment|
alignment_node = build_alignment_node(alignment)
next unless alignment_node

[
alignment_node,
*alignment.mapped_terms.map {|term| build_property_node(term) },
build_property_node(alignment.spine_term)
]
end
def class_node_id
build_uri("classMapping#{mapping.id}")
end

###
# @description: Specifies the format the alignment node should have.
###
# rubocop:disable Metrics/AbcSize
def build_alignment_node alignment
return if alignment.predicate.nil? || alignment.spine_term.nil?
def config_node
node = {
"@id": build_uri("mappingConfig"),
"@type": "desm:MappingConfiguration",
"dct:title": "Configuration for \"#{title}\" mapping"
}

node["dct:description"] = mapping.description if mapping.description?

node.merge(
"dct:created": created,
"dct:dateModified": updated,
"desm:hasClassMapping": class_node_id,
"desm:abstractClassType": domain_set_node.fetch(:"@id"),
"desm:mappingPredicateType": build_uri("MappingPredicates"),
"desm:hasDSO": organization_node_id
)
end

mapped_terms = alignment.mapped_terms.map {|mapped_term|
{"@id": mapped_term.uri} if mapped_term.uri.nil?
def domain_node
node = {
"@id": domain.source_uri,
"@type": "skos:Concept",
"skos:prefLabel": domain.pref_label
}

mapped_terms.compact!
return if mapped_terms.empty?
node["skos:definition"] = domain.definition if domain.definition?
node.merge("skos:inScheme": domain_set_node.fetch(:"@id"))
end

def domain_set_node
domain_set = domain.domain_set

node = {
"@id": "http://desmsolutions.org/TermMapping/#{alignment.id}",
"@type": "desm:TermMapping",
"dcterms:isPartOf": {"@id": "http://desmsolutions.org/TermMapping/#{@instance.id}"},
"desm:mappedterm": mapped_terms,
"desm:mappingPredicate": {"@id": alignment.predicate.uri},
"desm:spineTerm": {"@id": alignment.spine_term.uri}
"@id": build_uri("AbstractClasses"),
"dct:title": domain_set.title
}

node["desm:comment"] = alignment.comment if alignment.comment?
node["dct:description"] = domain_set.description if domain_set.description?
node
end

###
# @description: Defines the structure of a generic property term.
###
def build_property_node term
property = term.property
def organization_node
node = {
"@id": organization_node_id,
"@type": "desm:DSO",
"dct:title": organization.name
}

node["dct:description"] = organization.description if organization.description?
node["desm:homepage"] = organization.homepage_url if organization.homepage_url?
node.merge("desm:mapper": user_node_id)
end

def organization_node_id
build_uri(organization.slug)
end

def predicates_node
mapping.predicates.distinct.map do |predicate|
build_predicate_node(predicate)
end
end

def predicate_set_node
predicate_set = configuration_profile.mapping_predicates

node = {
"@id": term.source_uri,
"@type": "rdf:Property",
"desm:sourceURI": {"@id": property.source_uri},
"rdfs:label": term.property.label
"@id": build_uri("MappingPredicates"),
"dct:title": predicate_set.title
}

node["rdfs:comment"] = property.comment if property.comment?
node["rdfs:domain"] = {"@id": property.selected_domain} if property.selected_domain?
node["rdfs:range"] = {"@id": property.selected_range} if property.selected_range?
node["rdfs:subPropertyOf"] = {"@id": property.subproperty_of} if property.subproperty_of?
node["desm:valueSpace"] = {"@id": property.value_space} if property.value_space?
node["dct:description"] = predicate_set.description if predicate_set.description?
node
end

# rubocop:disable Metrics/AbcSize
def spec_node
node = {
"@id": spec_node_id,
"dct:title": specification.name,
"dct:creator": user_node_id
}

node["dct:description"] = specification.use_case if specification.use_case?
node["dct:hasVersion"] = specification.version if specification.version?
node["desm:AbstractClass"] = domain.source_uri
node["desm:hasProperty"] = term_nodes.map {|node| node.fetch(:"@id") }
node
end
# rubocop:enable Metrics/AbcSize

def spec_node_id
build_uri("Schema/#{specification.id}-#{specification.slug}")
end

def term_nodes
@term_nodes ||= [
*spine.terms.map {|term| build_term_node(term, spine: true) },
*mapping.terms.select("DISTINCT ON (terms.id) terms.*").map {|term| build_term_node(term) }
]
end

def user_node
node = {
"@id": user_node_id,
"sdo:name": user.fullname,
"sdo:email": user.email
}

node["sdo:telephone"] = user.phone if user.phone?
node["sdo:githubHandle"] = user.github_handle if user.github_handle?
node
end

def user_node_id
build_uri("Agent#{user.id}")
end
end
end

0 comments on commit d7069f8

Please sign in to comment.