Developers' FAQ - wvk/iqvoc GitHub Wiki
By default, the instance configuration comprises the following settings:
available_languages
languages.pref_labeling
languages.further_labelings.Labeling::SKOS::AltLabel
For some thesauri, it makes sense to distinguish primary and secondary languages - e.g. if preferred labels should always be in English, while other languages are allowed for alternative labels.
[TBD]
We use ActiveRecord to persist our data model in a relational database. That means you can use anything and everything that ActiveRecord offers. However, since iQvoc uses some performance optimisations, things might not always behave as you'd expect. For instance, all associations (concept-concept, concept-label, concept-note, collection-concept etc.) are modelled using the base classes and specificly typed relations (concept-prefLabel, concept-scopeNote etc.) are not, as one might expect, implemented as AR-scopes. Instead, all relations are always loaded in a single query and type filtering is done in memory, not in the database,
Example:
concept = iQvoc::Concept.base_class.find_by_origin('foobar')
concept.relations # => all relations
concept.relations.skos_broader # => only Concept::Relation::SKOS::Broader, but without an extra query
The following, however, will not work:
concept = iQvoc::Concept.base_class.find_by_origin('foobar')
concept.relations.skos_broader.new :target => my_other_concept
concept.relations.skos_broader = [my_other_concept, ...]
So basically, it's easiest to keep in mind that relations are read-only and use the triple-oriented write-API (RDFAPI).
The central point to access all write functionality ist Iqvoc::RDFAPI
. It is an extensible API that for now supports well-formed NB-Triples and the iQvoc canonical triple format (a somewhat stripped down version of N-Triples). RDFAPI implements the method Iqvoc::RDFAPI.parse_triple("subject predicate object")
. It takes a String argument and returns the evaluated triple, which depends on what you feed it. Best to demonstrate that with some examples:
Example: creating a new Concept:
concept = Iqvoc::RDFAPI.parse_triple ':foo rdf:type skos:Concept'
Example: adding a concept to a collection
# create the collection:
collection = Iqvoc::RDFAPI.parse_triple ':bar rdf:type skos:Collection'
member = Iqvoc::RDFAPI.parse_triple ':bar skos:member :foo'
Example: setting/changing a preferred label for a Concept
labeling_en = Iqvoc::RDFAPI.parse_triple ':foo skos:prefLabel "My Concept"@en'
labeling_de = Iqvoc::RDFAPI.parse_triple ':foo skos:prefLabel "Mein Konzept"@de'
Example: Add relations between Concepts:
simple_relation = Iqvoc::RDFAPI.parse_triple ':concept1 skos:related :concept2'
broader_relation = Iqvoc::RDFAPI.parse_triple ':concept1 skos:broader :concept3'
NOTE: this will create the inverse relation (skos:narrower) as well, so there's no need for this:
inverse_relation = Iqvoc::RDFAPI.parse_triple ':concept2 skos:broader :concept1'
narrower_relation = Iqvoc::RDFAPI.parse_triple ':concept3 skos:narrower :concept1'
If you are not interested in the intermediate results of parsing single triples but instead want to feed the RDFAPI lots of lines, you can do so as well using the aptly named parse_triples
method:
Iqvoc::RDFAPI.parse_triples <<-EOT
:concept1 rdf:type skos:Concept
:concept1 skos:prefLabel "Yadda"@en
:concept2 rdf:type skos:Concept
:concept2 skos:prefLabel "Yodda"@en
:concept1 skos:related :concept2
EOT
parse_triples
basically works like calling parse_triple
for each line and calling save
on the resulting object.
The above can equivalently be done with N-Triples instead of the canonical triple format:
string = <<-EOT
<http://example.org/concept1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2008/05/skos#Concept> .
<http://example.org/concept1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#prefLabel> "Yadda"@en .
<http://example.org/concept2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2008/05/skos#Concept> .
<http://example.org/concept2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#prefLabel> "Yodda"@en .
<http://example.org/concept1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#related> <http://example.org/concept2> .
EOT
Iqvoc::RDFAPI.parse_nt string, 'http://example.org/'
The RDFAPI identifies objects by their origin (:concept1 and :concept2 in the above example) and uses a cache to avoid excessive database lookups. However, this cache is being emptied (deliberately) on each successive parse_triples
call in order to avoid stale data between imports. After an import however, you are free to access any imported object using the cached
method:
Iqvoc::RDFAPI.cached(:concept1) # returns a Concept::SKOS::Base with origin ':concept1'
If there is no concept with that origin in the cace, it is loaded transparently from the atabase if it can be found there. Otherwise, nil is returned. RDFAPI guarantees that the Object returned by cached
always represents the (one) record in the database (if any), so saving it will never result in a duplicate database record.
The RDFAPI consists of several distinct parts:
- Grammar: a set of nested Regexen, encapsulated in different modules for different RDF serializations. Currently, only an iQvoc Canonical and the N-Triples format are implemented, more are expected to be implemenmted.
- Parser: Uses the grammar to parse any input (line-wise) into a canonical token format which can be interpreted by the receiving classes.
- Interpreter: There is not an actual separate interpreter instance, but each Ruby class implementing an RDF 'thing' (either object or predicate) has to provide a
build_from_parsed_tokens
method with asave
able return value. - Object cache: To allow object lookups via their 'origin' (as the : part of an identifier is called in iQvoc),
RDFAPI.cached(:origin)
allows access to already processed 'first level objects' (i.e. anything with an origin, such as Concepts and Collections, or Labels in SKOS-XL). This cache is emptied on eachparse_triples
call.