iqvoc 4.7.0 → 4.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Gemfile +11 -11
  4. data/Gemfile.lock +178 -122
  5. data/README.md +39 -24
  6. data/{test/performance/browsing_test.rb → app/aides/inline_data_helper.rb} +23 -6
  7. data/app/aides/maker.rb +139 -0
  8. data/{lib → app/aides}/multi_logger.rb +0 -0
  9. data/app/aides/origin.rb +47 -0
  10. data/app/aides/rdfapi.rb +59 -0
  11. data/app/aides/skos_exporter.rb +151 -0
  12. data/app/aides/skos_importer.rb +348 -0
  13. data/app/assets/javascripts/iqvoc/entityselect.js.erb +7 -9
  14. data/app/controllers/application_controller.rb +1 -3
  15. data/app/controllers/collections/versions_controller.rb +1 -3
  16. data/app/controllers/concepts/versions_controller.rb +9 -3
  17. data/app/controllers/concerns/controller_extensions.rb +109 -0
  18. data/app/{concerns → controllers/concerns}/reverse_match_errors.rb +0 -0
  19. data/app/controllers/hierarchy_controller.rb +7 -3
  20. data/app/controllers/instance_configuration_controller.rb +1 -1
  21. data/app/controllers/pages_controller.rb +10 -0
  22. data/app/controllers/search_results_controller.rb +2 -2
  23. data/app/controllers/triplestore_sync_controller.rb +2 -4
  24. data/app/helpers/application_helper.rb +1 -1
  25. data/app/helpers/widget_helper.rb +3 -3
  26. data/app/jobs/export_job.rb +1 -3
  27. data/app/jobs/import_job.rb +1 -3
  28. data/app/models/ability.rb +59 -0
  29. data/app/models/abstract_user.rb +1 -1
  30. data/app/models/collection/base.rb +12 -3
  31. data/app/models/collection/member/skos/base.rb +1 -1
  32. data/app/models/concept/base.rb +15 -8
  33. data/app/models/concept/relation/base.rb +1 -1
  34. data/app/models/concept/relation/skos/base.rb +1 -1
  35. data/app/models/concept/skos/scheme.rb +1 -1
  36. data/app/models/concept/validations.rb +1 -1
  37. data/app/models/concerns/deep_cloning.rb +92 -0
  38. data/app/models/concerns/first_level_object_scopes.rb +9 -0
  39. data/app/{concerns → models/concerns}/first_level_object_validations.rb +9 -2
  40. data/app/models/concerns/rankable.rb +31 -0
  41. data/app/models/{search_extension.rb → concerns/search_extension.rb} +0 -0
  42. data/app/{concerns → models/concerns}/versioning.rb +0 -6
  43. data/app/models/configuration_setting.rb +1 -1
  44. data/app/models/labeling/skos/base.rb +2 -2
  45. data/app/models/match/skos/base.rb +2 -2
  46. data/app/models/note/skos/base.rb +7 -6
  47. data/app/models/note/skos/change_note.rb +1 -1
  48. data/{lib/iqvoc/rdf_sync.rb → app/services/rdf_sync_service.rb} +3 -3
  49. data/app/view_models/concept_view.rb +1 -1
  50. data/app/views/collections/_form.html.erb +2 -2
  51. data/app/views/concepts/scheme/edit.html.erb +1 -1
  52. data/app/views/pages/components.html.erb +45 -0
  53. data/app/views/pages/version.html.erb +6 -0
  54. data/app/views/partials/concept/_reverse_match_notice.html.erb +0 -1
  55. data/app/views/search_results/_sidebar.html.erb +3 -3
  56. data/config/application.rb +4 -1
  57. data/config/boot.rb +1 -2
  58. data/config/database.yml.postgresql +23 -0
  59. data/config/engine.rb +0 -2
  60. data/config/environments/heroku.rb +1 -1
  61. data/config/initializers/inflections.rb +9 -3
  62. data/config/initializers/iqvoc.rb +1 -7
  63. data/config/initializers/mime_types.rb +0 -1
  64. data/config/locales/de.yml +2 -1
  65. data/config/locales/en.yml +11 -10
  66. data/config/routes.rb +2 -0
  67. data/config/travis/database.yml.mysql +9 -0
  68. data/config/travis/database.yml.postgresql +7 -0
  69. data/config/travis/database.yml.sqlite +5 -0
  70. data/db/migrate/20141204151558_add_foreign_key_constraints.rb +23 -0
  71. data/iqvoc.gemspec +2 -2
  72. data/lib/generators/app/template.rb +15 -7
  73. data/lib/iqvoc.rb +2 -1
  74. data/lib/iqvoc/configuration/core.rb +18 -4
  75. data/lib/iqvoc/configuration/instance_configuration.rb +125 -0
  76. data/lib/iqvoc/configuration/navigation.rb +63 -0
  77. data/lib/iqvoc/environments/development.rb +4 -0
  78. data/lib/iqvoc/environments/production.rb +11 -12
  79. data/lib/iqvoc/environments/test.rb +4 -1
  80. data/lib/iqvoc/version.rb +2 -2
  81. data/lib/tasks/exporter.rake +1 -4
  82. data/lib/tasks/importer.rake +1 -5
  83. data/lib/tasks/sync.rake +1 -2
  84. data/test/controllers/concept_movement_test.rb +11 -11
  85. data/test/controllers/hierarchy_test.rb +83 -79
  86. data/test/controllers/reverse_match_test.rb +2 -2
  87. data/test/integration/alphabetical_test.rb +2 -3
  88. data/test/integration/browse_concepts_and_labels_test.rb +2 -2
  89. data/test/integration/collection_circularity_test.rb +6 -6
  90. data/test/integration/concept_scheme_browsing_test.rb +2 -2
  91. data/test/integration/edit_concepts_test.rb +1 -1
  92. data/test/integration/export_test.rb +5 -3
  93. data/test/integration/import_test.rb +4 -1
  94. data/test/integration/instance_configuration_browsing_test.rb +2 -2
  95. data/test/integration/navigation_test.rb +2 -2
  96. data/test/integration/note_annotations_test.rb +12 -11
  97. data/test/integration/reverse_match_job_test.rb +19 -10
  98. data/test/integration/search_test.rb +6 -6
  99. data/test/integration/tree_test.rb +3 -3
  100. data/test/integration/untranslated_test.rb +1 -1
  101. data/test/models/concept_test.rb +13 -14
  102. data/test/models/inline_data_test.rb +9 -9
  103. data/test/models/instance_configuration_test.rb +7 -3
  104. data/test/models/origin_test.rb +9 -59
  105. data/test/models/rdf_sync_test.rb +2 -4
  106. data/test/models/rdfapi_test.rb +0 -2
  107. data/test/models/skos_collection_import_test.rb +3 -4
  108. data/test/models/skos_export_test.rb +3 -5
  109. data/test/models/skos_import_test.rb +12 -10
  110. data/test/test_helper.rb +0 -1
  111. data/vendor/assets/stylesheets/{jquery-ui.css.scss → jquery-ui.scss} +0 -0
  112. data/vendor/assets/stylesheets/{jquery-ui.structure.css.scss → jquery-ui.structure.scss} +0 -0
  113. data/vendor/assets/stylesheets/{jquery-ui.theme.css.scss → jquery-ui.theme.scss} +0 -0
  114. metadata +34 -28
  115. data/lib/iqvoc/ability.rb +0 -60
  116. data/lib/iqvoc/controller_extensions.rb +0 -111
  117. data/lib/iqvoc/deep_cloning.rb +0 -90
  118. data/lib/iqvoc/inline_data_helper.rb +0 -45
  119. data/lib/iqvoc/instance_configuration.rb +0 -123
  120. data/lib/iqvoc/maker.rb +0 -141
  121. data/lib/iqvoc/navigation.rb +0 -61
  122. data/lib/iqvoc/origin.rb +0 -111
  123. data/lib/iqvoc/rankable.rb +0 -33
  124. data/lib/iqvoc/rdfapi.rb +0 -60
  125. data/lib/iqvoc/skos_exporter.rb +0 -153
  126. data/lib/iqvoc/skos_importer.rb +0 -337
data/README.md CHANGED
@@ -4,30 +4,34 @@
4
4
  [![Build Status](https://secure.travis-ci.org/innoq/iqvoc.png)](http://travis-ci.org/innoq/iqvoc)
5
5
  [![Code Climate](https://codeclimate.com/github/innoq/iqvoc.png)](https://codeclimate.com/github/innoq/iqvoc)
6
6
 
7
- iQvoc is a vocabulary management tool that combines easy-to-use human interfaces with Semantic Web interoperability.
7
+ iQvoc is a vocabulary management tool that combines easy-to-use human interfaces
8
+ with Semantic Web interoperability.
8
9
 
9
- iQvoc supports vocabularies that are common to many knowledge organization systems, such as:
10
+ iQvoc supports vocabularies that are common to many knowledge organization
11
+ systems, such as:
10
12
 
11
13
  * Thesauri
12
14
  * Taxonomies
13
15
  * Classification schemes
14
16
  * Subject heading systems
15
17
 
16
- iQvoc provides comprehensive functionality for all aspects of managing such vocabularies:
18
+ iQvoc provides comprehensive functionality for all aspects of managing such
19
+ vocabularies:
17
20
 
18
21
  * import of existing vocabularies from a SKOS representation
19
22
  * multilingual display and navigation in any Web browser
20
23
  * editorial features for registered users
21
24
  * publishing the vocabulary in the Semantic Web
22
25
 
23
- iQvoc is built with state-of-the-art technology and can be easily customized according to user's needs.
24
-
26
+ iQvoc is built with state-of-the-art technology and can be easily customized
27
+ according to user's needs.
25
28
  ## Setup
26
29
 
27
30
  ### Heroku
28
31
 
29
- You can easily setup your iQvoc instance in under 5 minutes, we wanted to make this process really easy.
30
- In order to deploy to heroku you need to have an account and [heroku toolbelt](https://toolbelt.heroku.com) installed.
32
+ You can easily setup your iQvoc instance in under 5 minutes, we wanted to make
33
+ this process really easy. In order to deploy to heroku you need to have an
34
+ account and [heroku toolbelt](https://toolbelt.heroku.com) installed.
31
35
 
32
36
  ```
33
37
  $ bundle install
@@ -46,23 +50,26 @@ Remember to visit the Users section and change the default passwords!
46
50
  ### Custom
47
51
 
48
52
  We recommend running [iQvoc as a Rails engine](https://github.com/innoq/iqvoc/wiki/iQvoc-as-a-Rails-Engine).
49
- Running the cloned source code is possible but any modifications would require a fork.
53
+ Running the cloned source code is possible but any modifications would require a
54
+ fork.
50
55
 
51
- 1. Configure your database via `config/database.template.yml`. Don't forget to rename it to `database.yml`
56
+ 1. Configure your database via `config/database.template.yml`.
57
+ Don't forget to rename it to `database.yml`
52
58
  2. Run `bundle install`
53
59
  3. Run `bundle exec rake db:create` to create the database
54
60
  4. Create the necessary tables by running `rake db:migrate`
55
61
  5. Load some base data by running `rake db:seed`
56
62
  6. Make sure you have got `config/secrets.yml` in place
57
- 7. Boot up the app using `bundle exec rails s` (or `passenger start` if you use passenger)
63
+ 7. Boot up the app using `bundle exec rails s` (or `passenger start`
64
+ if you use passenger)
58
65
  8. Log in with "admin@iqvoc" / "admin" or "demo@iqvoc" / "cooluri" (cf. step #5)
59
66
  9. Visit the Users section and change the default passwords
60
67
 
61
68
  ## Background Jobs
62
69
 
63
- Note that some features like "Import" and "Export" exposed in the Web UI store their workload
64
- as jobs. You can either issue a job worker that runs continuously and watches
65
- for new jobs via
70
+ Note that some features like "Import" and "Export" exposed in the Web UI store
71
+ their workload as jobs. You can either issue a job worker that runs continuously
72
+ and watches for new jobs via
66
73
 
67
74
  ```
68
75
  $ rake jobs:work
@@ -76,13 +83,14 @@ $ rake jobs:workoff
76
83
 
77
84
  ## Compatibility
78
85
 
79
- iQvoc is fully compatible with Ruby 1.9, 2.0, 2.1 and JRuby 1.7.
86
+ iQvoc is fully compatible with Ruby 1.9, 2.0, 2.1 and JRuby 1.7
87
+ (in Ruby 1.9-mode).
80
88
 
81
89
  ## Customization
82
90
 
83
- There are many hooks providing support for your own classes and configuration. The core app
84
- also works as a Rails Engine. The config residing in `lib/iqvoc.rb` provides a basic
85
- overview of the possibilities.
91
+ There are many hooks providing support for your own classes and configuration.
92
+ The core app also works as a Rails Engine. The config residing in `lib/iqvoc.rb`
93
+ provides a basic overview of the possibilities.
86
94
 
87
95
  ## Documentation
88
96
 
@@ -100,13 +108,19 @@ For more information on SemVer, visit http://semver.org/.
100
108
 
101
109
  If you want to help out there are several options:
102
110
 
103
- * Found a bug? Just create an issue on the [GitHub Issue tracker](https://github.com/innoq/iqvoc/issues) and/or submit a patch by initiating a pull request
104
- * You're welcome to fix bugs listed under [Issues](https://github.com/innoq/iqvoc/issues)
105
- * Proposal, discussion and implementation of new features on our mailing list iqvoc@lists.innoq.com or on the issue tracker
111
+ - Found a bug? Just create an issue on the
112
+ [GitHub Issue tracker](https://github.com/innoq/iqvoc/issues) and/or submit a
113
+ patch by initiating a pull request
114
+ - You're welcome to fix bugs listed under
115
+ [Issues](https://github.com/innoq/iqvoc/issues)
116
+ - Proposal, discussion and implementation of new features on our mailing list
117
+ [iqvoc@lists.innoq.com] or on the issue tracker
106
118
 
107
- If you make changes to existing code please make sure that the test suite stays green. Please include tests to your additional contributions.
119
+ If you make changes to existing code please make sure that the test suite stays
120
+ green. Please include tests to your additional contributions.
108
121
 
109
- Tests can be run via `bundle exec rake test`. We're using capybara-webkit for integration tests with JavaScript support.
122
+ Tests can be run via `bundle exec rake test`. We're using Poltergeist for
123
+ integration tests with JavaScript support.
110
124
 
111
125
  ## Maintainer & Contributors
112
126
 
@@ -115,9 +129,10 @@ iQvoc was originally created and is being maintained by [innoQ Deutschland GmbH]
115
129
  * Robert Glaser ([mrreynolds](http://github.com/mrreynolds))
116
130
  * Till Schulte-Coerne ([tillsc](http://github.com/tillsc))
117
131
  * Frederik Dohr ([FND](http://github.com/FND))
132
+ * Marc Jansing ([mjansing](http://github.com/mjansing))
118
133
 
119
134
  ## License
120
135
 
121
- Copyright 2014 innoQ Deutschland GmbH
136
+ Copyright 2014 [innoQ Deutschland GmbH](https://www.innoq.com).
122
137
 
123
- Licensed under the Apache License, Version 2.0
138
+ Licensed under the Apache License, Version 2.0.
@@ -14,12 +14,29 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- require File.join(File.expand_path(File.dirname(__FILE__)), '../test_helper')
18
- require 'rails/performance_test_help'
17
+ require 'csv'
19
18
 
20
- # Profiling results for each test method are written to tmp/performance.
21
- class BrowsingTest < ActionDispatch::PerformanceTest
22
- def test_homepage
23
- get '/'
19
+ class InlineDataHelper
20
+ JOINER = ', '
21
+ SPLITTER = /[,\n] */
22
+
23
+ CSV_OPTIONS = {
24
+ col_sep: ', ',
25
+ quote_char: '"'
26
+ }
27
+
28
+ def self.parse_inline_values(inline_values)
29
+ options = CSV_OPTIONS.clone
30
+ options[:col_sep] = options[:col_sep].strip
31
+ begin
32
+ values = inline_values.parse_csv(options)
33
+ rescue CSV::MalformedCSVError => exc
34
+ values = inline_values.parse_csv(CSV_OPTIONS)
35
+ end
36
+ values ? values.map(&:strip) : []
37
+ end
38
+
39
+ def self.generate_inline_values(values)
40
+ values.to_csv(CSV_OPTIONS).strip
24
41
  end
25
42
  end
@@ -0,0 +1,139 @@
1
+ module Maker
2
+ # labels:
3
+ # -
4
+ # value: <string>
5
+ # <attribute>: <value>
6
+ # inflectionals: [<string>, ...]
7
+ # components: [<label>, ...]
8
+ #
9
+ # concepts:
10
+ # -
11
+ # <attribute>: <value>
12
+ # pref_labels: [<label>, ...]
13
+ # alt_labels: [<label>, ...]
14
+ # broader: <concept>
15
+ # narrower: <concept>
16
+ # related: [<concept>, ...]
17
+ #
18
+ # NB:
19
+ # * <label> and <concept> can either be strings referencing a previously
20
+ # declared entity or objects representing a new entity
21
+ # XXX: The latter is not currently supported yet!
22
+ # * order of concepts matters when referencing relations
23
+ # * order of labels matters when referencing components
24
+ def self.from_yaml(yml)
25
+ data = YAML.load(yml)
26
+
27
+ labels = {}
28
+ data['labels'].each { |label| # XXX: use omap to simplify format (making `value` the key instead of an attribute)?
29
+ term = label.delete('value')
30
+
31
+ components = label.delete('components').map { |term|
32
+ labels[term]
33
+ } if label['components']
34
+
35
+ options = {
36
+ inflectionals: label.delete('inflectionals'),
37
+ components: components,
38
+ label_attributes: label
39
+ }
40
+
41
+ labels[term] = self.label(term, options)
42
+ } if data['labels']
43
+
44
+ concepts = {}
45
+ data['concepts'].each { |concept| # XXX: use omap to simplify format (using a single pref_label as key)?
46
+ relations = {}
47
+ ['broader', 'narrower'].each { |type| # TODO: missing related, support for poly-hierarchies
48
+ relations[type] = concepts[concept.delete(type)] if concept[type]
49
+ }
50
+
51
+ lbls = {} # TODO: rename
52
+ ['pref', 'alt'].each { |type| # TODO: missing hidden
53
+ key = "#{type}_labels"
54
+ lbls[type] = concept.delete(key).map { |term|
55
+ labels[term]
56
+ } if concept[key]
57
+ }
58
+
59
+ options = {
60
+ pref_labels: lbls['pref'],
61
+ alt_labels: lbls['alt'],
62
+ concept_attributes: concept
63
+ }
64
+
65
+ identifier = options[:pref_labels].first.value
66
+ concepts[identifier] = self.concept(options)
67
+ concepts[identifier].send(Iqvoc::Concept.broader_relation_class.name.to_relation_name).
68
+ create_with_reverse_relation(relations['broader']) if relations['broader']
69
+ concepts[identifier].send(Iqvoc::Concept.broader_relation_class.narrower_class.name.to_relation_name).
70
+ create_with_reverse_relation(relations['narrower']) if relations['narrower']
71
+ } if data['concepts']
72
+
73
+ return { concepts: concepts, labels: labels }
74
+ end
75
+
76
+ # optional arguments:
77
+ # concept_attributes for custom concept attributes
78
+ # pref_labels is an array of strings or label instances to be used as prefLabels
79
+ # alt_labels is an array of strings or label instances to be used as altLabels
80
+ def self.concept(options={})
81
+ attributes = options[:concept_attributes] || {}
82
+ pref_labels = options[:pref_labels] || []
83
+ alt_labels = options[:alt_labels] || []
84
+
85
+ defaults = { # NB: must use strings, not symbols as keys due to YAML
86
+ 'published_at' => 3.days.ago
87
+ }
88
+ attributes = defaults.merge(attributes)
89
+
90
+ concept = Iqvoc::Concept.base_class.create!(attributes)
91
+
92
+ pref_labels.each { |term|
93
+ label = term.is_a?(String) ? self.label(term) : term
94
+ Iqvoc::Concept.pref_labeling_class.
95
+ create!(owner: concept, target: label)
96
+ }
97
+ alt_labels.each { |term|
98
+ label = term.is_a?(String) ? self.label(term) : term
99
+ Iqvoc::Concept.further_labeling_classes.first.first.
100
+ create!(owner: concept, target: label)
101
+ }
102
+
103
+ return concept
104
+ end
105
+
106
+ # optional arguments:
107
+ # label_attributes for custom label attributes
108
+ # inflectionals is an array of strings to be used as inflectionals
109
+ # components is an array of labels to be used as compound form contents
110
+ def self.label(value, options={}) # FIXME: move into SKOS-XL extension
111
+ attributes = options[:label_attributes] || {}
112
+ inflectionals = options[:inflectionals] || []
113
+ components = options[:components] || []
114
+
115
+ defaults = { # NB: must use strings, not symbols as keys due to YAML
116
+ value: value, # intentionally not a string; symbol takes precedence
117
+ 'origin' => Origin.new(value).to_s,
118
+ 'language' => Iqvoc::Concept.pref_labeling_languages.first,
119
+ 'published_at' => 2.days.ago
120
+ }
121
+ attributes = defaults.merge(attributes)
122
+
123
+ klass = Iqvoc::XLLabel rescue Iqvoc::Label # FIXME: breaks encapsulation (hard-coded iqvoc_skosxl dependency)
124
+ label = klass.base_class.create!(attributes)
125
+
126
+ inflectionals.each { |inf|
127
+ label.inflectionals.create!(value: inf)
128
+ }
129
+
130
+ if components.length > 0
131
+ compound_form_contents = components.each_with_index.map { |label, i|
132
+ CompoundForm::Content::Base.new(label: label, order: i)
133
+ }
134
+ label.compound_forms.create!(compound_form_contents: compound_form_contents)
135
+ end
136
+
137
+ return label
138
+ end
139
+ end
File without changes
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2011-2013 innoQ Deutschland GmbH
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ class Origin
18
+ attr_accessor :initial_value, :value
19
+
20
+ def initialize(value = nil)
21
+ self.initial_value = value
22
+ self.value = "_#{SecureRandom.hex(4)}"
23
+ end
24
+
25
+ def valid?
26
+ valid = true
27
+
28
+ if blank_node = initial_value.match(RDFAPI::BLANK_NODE_REGEXP)
29
+ # blank node validation, should not contain special chars
30
+ valid = false if CGI.escape(blank_node[1]) != blank_node[1]
31
+ else
32
+ # regular subject validation
33
+
34
+ # should not start with a number
35
+ valid = false if initial_value.match(/^\d.*/)
36
+
37
+ # should not contain special chars
38
+ valid = false if CGI.escape(initial_value) != initial_value
39
+ end
40
+
41
+ valid
42
+ end
43
+
44
+ def to_s
45
+ value
46
+ end
47
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2011-2013 innoQ Deutschland GmbH
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ class RDFAPI
18
+ FIRST_LEVEL_OBJECT_CLASSES = [Iqvoc::Concept.base_class, Iqvoc::Collection.base_class]
19
+ SECOND_LEVEL_OBJECT_CLASSES = Iqvoc::Concept.labeling_classes.keys +
20
+ Iqvoc::Concept.note_classes +
21
+ Iqvoc::Concept.relation_classes +
22
+ Iqvoc::Concept.match_classes +
23
+ Iqvoc::Concept.notation_classes +
24
+ Iqvoc::Concept.additional_association_classes.keys +
25
+ [Iqvoc::Collection.member_class]
26
+
27
+ OBJECT_DICTIONARY = FIRST_LEVEL_OBJECT_CLASSES.inject({}) do |hash, klass|
28
+ hash["#{klass.rdf_namespace}:#{klass.rdf_class}"] = klass
29
+ hash
30
+ end
31
+
32
+ PREDICATE_DICTIONARY = SECOND_LEVEL_OBJECT_CLASSES.inject({}) do |hash, klass|
33
+ hash["#{klass.rdf_namespace}:#{klass.rdf_predicate}"] = klass
34
+ hash
35
+ end
36
+
37
+ URI_REGEXP = /^https?:\/\/[^\s]+$/
38
+ LITERAL_REGEXP = /^"(.+)"(@(.+))?$/
39
+ BLANK_NODE_REGEXP = /^_:(.+)/
40
+
41
+ def self.devour(rdf_subject, rdf_predicate, rdf_object)
42
+ case rdf_predicate
43
+ when 'a', 'rdf:type'
44
+ case rdf_object
45
+ when String
46
+ target = OBJECT_DICTIONARY[rdf_object] || rdf_object.constantize
47
+ else
48
+ target = rdf_object
49
+ end
50
+ target.find_or_initialize_by(origin: rdf_subject)
51
+ when String
52
+ # dictionary lookup
53
+ target = PREDICATE_DICTIONARY[rdf_predicate] || rdf_predicate.constantize
54
+ target.build_from_rdf(rdf_subject, target, rdf_object)
55
+ else # is a class
56
+ rdf_predicate.build_from_rdf(rdf_subject, rdf_predicate, rdf_object)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,151 @@
1
+ require 'iq_rdf'
2
+ require 'uri'
3
+ require 'fileutils'
4
+
5
+ class SkosExporter
6
+ include RdfHelper # necessary to use render_concept helper
7
+ include RdfNamespacesHelper
8
+ include Rails.application.routes.url_helpers
9
+
10
+ def initialize(file_path, type, default_namespace_url, logger = Rails.logger)
11
+ default_url_options[:port] = URI.parse(default_namespace_url).port
12
+ default_url_options[:host] = URI.parse(default_namespace_url).to_s.gsub(/\/$/, '')
13
+
14
+ @file_path = file_path
15
+ @type = type
16
+ @logger = logger
17
+ @document = IqRdf::Document.new
18
+
19
+ unless ['ttl', 'nt', 'xml'].include? @type
20
+ raise "SkosExporter: Unknown rdf serialization. Parameter 'type' should be 'ttl' (Turtle), 'nt' (N-Triples) or 'xml' (RDF-XML)."
21
+ end
22
+
23
+ unless @file_path.is_a?(String)
24
+ raise "SkosExporter#export: Parameter 'file' should be a String."
25
+ end
26
+ end
27
+
28
+ def run
29
+ export
30
+ end
31
+
32
+ private
33
+
34
+ def export
35
+ ActiveSupport.run_load_hooks(:rdf_export_before, self)
36
+
37
+ start = Time.now
38
+ @logger.info 'Starting export...'
39
+ @logger.info "file_path = #{@file_path}"
40
+ @logger.info "type = #{@type}"
41
+
42
+ # add export data
43
+ add_namespaces(@document)
44
+ add_collections(@document)
45
+ add_concepts(@document)
46
+
47
+ ActiveSupport.run_load_hooks(:rdf_export_before_save, self)
48
+
49
+ # saving export to disk
50
+ save_file(@file_path, @type, @document)
51
+
52
+ done = Time.now
53
+ @logger.info "Export Job finished in #{(done - start).to_i} seconds."
54
+
55
+ ActiveSupport.run_load_hooks(:rdf_export_after, self)
56
+ end
57
+
58
+ def add_namespaces(document)
59
+ @logger.info 'Exporting namespaces...'
60
+
61
+ RdfNamespacesHelper.instance_methods.each do |meth|
62
+ namespaces = send(meth)
63
+ document.namespaces(namespaces) if namespaces.is_a?(Hash)
64
+ end
65
+
66
+ @logger.info 'Finished exporting namespaces.'
67
+ end
68
+
69
+ def add_collections(document)
70
+ @logger.info 'Exporting collections...'
71
+
72
+ offset = 0
73
+ while true
74
+ collections = Iqvoc::Collection.base_class.published.order('id').limit(100).offset(offset)
75
+ limit = collections.size < 100 ? collections.size : 100
76
+ break if collections.size == 0
77
+
78
+ # Todo: Preloading???
79
+ collections.each do |collection|
80
+ render_collection(document, collection)
81
+ end
82
+
83
+ @logger.info "Collections #{offset+1}-#{offset+limit} exported."
84
+ offset += collections.size # Size is important!
85
+ end
86
+
87
+ @logger.info "Finished exporting collections (#{offset} collections exported)."
88
+ end
89
+
90
+ def add_concepts(document)
91
+ @logger.info 'Exporting concepts...'
92
+
93
+ offset = 0
94
+ while true
95
+ concepts = Iqvoc::Concept.base_class.published.order('id').limit(100).offset(offset)
96
+ limit = concepts.size < 100 ? concepts.size : 100
97
+ break if concepts.size == 0
98
+
99
+ # When in single query mode, AR handles ALL includes to be loaded by that
100
+ # one query. We don't want that! So let's do it manually :-)
101
+ ActiveRecord::Associations::Preloader.new.preload(concepts,
102
+ Iqvoc::Concept.base_class.default_includes + [
103
+ :matches,
104
+ :collection_members,
105
+ :notations,
106
+ { relations: :target, labelings: :target, notes: :annotations }
107
+ ])
108
+
109
+ concepts.each do |concept|
110
+ render_concept(document, concept, true)
111
+ end
112
+
113
+ @logger.info "Concepts #{offset+1}-#{offset+limit} exported."
114
+ offset += concepts.size # Size is important!
115
+ end
116
+
117
+ @logger.info "Finished exporting concepts (#{offset} concepts exported)."
118
+ end
119
+
120
+ def save_file(file_path, type, content)
121
+ begin
122
+ @logger.info "Saving export to '#{@file_path}'"
123
+ create_directory(@file_path)
124
+ file = File.open(@file_path, 'w')
125
+ content = serialize_rdf(content, type)
126
+ file.write(content)
127
+ rescue IOError => e
128
+ # some error occur
129
+ # e.g not writable
130
+ ensure
131
+ file.close unless file == nil
132
+ end
133
+ end
134
+
135
+ def create_directory(file_path)
136
+ dirname = File.dirname(file_path)
137
+ unless File.directory?(dirname)
138
+ FileUtils.mkdir_p(dirname)
139
+ end
140
+ end
141
+
142
+ def serialize_rdf(document, type)
143
+ if type == 'xml'
144
+ document.to_xml
145
+ elsif type == 'ttl'
146
+ document.to_turtle
147
+ else
148
+ document.to_ntriples
149
+ end
150
+ end
151
+ end