uri_service 0.2.12 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c6dfdd9146dcc210aeddc11463baa748805e8f80
4
- data.tar.gz: 495d0322eae8b105d97af658c41f6be059b895e3
3
+ metadata.gz: 5012df111508e186ecb439067488e70ec5e511e5
4
+ data.tar.gz: af2dbf25aec0882474d1a8909cc26fd81007b1b8
5
5
  SHA512:
6
- metadata.gz: 16ce2155b663b3f042bf6530e573ece4b96e6552d1f11e6d4b74347e359ed850ce4aec9264245bdd14ee1cbc858d5552d762ab57ab62f04071e42281449834bc
7
- data.tar.gz: 36acf7e9108320b1d44da61cd5758b842cd3627bfe78a02ce2e8015df99576ea878254f44428dd0d4344fb6599cf94dc77cae8a1b0802e691277eb7cfa1d0a7f
6
+ metadata.gz: 8a9c898ce08c8f34a2ca4d23f088b24c9d735cd3ac2ecf1f565bb47584ca2e46a333d89b956da404f56b4f5d517c8ee3625b16a45b555e60ee0fc5edeea4f704
7
+ data.tar.gz: f6b423ecf9d89676b3661fd027ca5ba14bca37affffe3bb9e16415559d0de451b637554e7fc774b46339a4d108cb4d9b1f63f94690eeb93d7f080cf9fb2289ee
data/README.md CHANGED
@@ -15,6 +15,7 @@ gem install uri_service
15
15
  ```ruby
16
16
  UriService::init({
17
17
  'local_uri_base' => 'http://id.example.com/term/',
18
+ 'temporary_uri_base' => 'com:example:id:temporary:',
18
19
  'solr' => {
19
20
  'url' => 'http://localhost:8983/solr/uri_service_test',
20
21
  'pool_size' => 5,
@@ -36,6 +37,7 @@ UriService.client.do_stuff(...)
36
37
  ```ruby
37
38
  client = UriService::Client.new({
38
39
  'local_uri_base' => 'http://id.example.com/term/',
40
+ 'temporary_uri_base' => 'com:example:id:temporary:',
39
41
  'solr' => {
40
42
  'url' => 'http://localhost:8983/solr/uri_service_test',
41
43
  'pool_size' => 5,
@@ -65,6 +67,7 @@ Note that the database that you specify here does not have to be the same databa
65
67
  ```yaml
66
68
  development:
67
69
  local_uri_base: 'http://id.example.com/term/'
70
+ temporary_uri_base: 'com:example:id:temporary:'
68
71
  solr:
69
72
  url: 'http://localhost:8983/solr/uri_service_development'
70
73
  pool_size: 5
@@ -77,6 +80,7 @@ development:
77
80
 
78
81
  test:
79
82
  local_uri_base: 'http://id.example.com/term/'
83
+ temporary_uri_base: 'com:example:id:temporary:'
80
84
  solr:
81
85
  url: 'http://localhost:8983/solr/uri_service_test'
82
86
  pool_size: 5
@@ -89,6 +93,7 @@ test:
89
93
 
90
94
  production:
91
95
  local_uri_base: 'http://id.example.com/term/'
96
+ temporary_uri_base: 'com:example:id:temporary:'
92
97
  solr:
93
98
  url: 'http://localhost:9983/solr/uri_service_production'
94
99
  pool_size: 5
data/lib/uri_service.rb CHANGED
@@ -12,9 +12,10 @@ module UriService
12
12
  VOCABULARIES = :vocabularies
13
13
  TERM = :term
14
14
  TERMS = :terms
15
+ VALID_URI_REGEX = /\A#{URI::regexp}\z/
15
16
 
16
17
  # Initialize the main instance of UriService::Client
17
- # opts format: { 'local_uri_base' => 'http://id.example.com/term/', 'solr' => {...solr config...}, 'database' => {...database config...} }
18
+ # opts format: { 'local_uri_base' => 'http://id.example.com/term/', temporary_uri_base: 'temporary:', 'solr' => {...solr config...}, 'database' => {...database config...} }
18
19
  def self.init(opts)
19
20
  if @client && @client.connected?
20
21
  @client.disconnect!
@@ -47,6 +48,7 @@ module UriService
47
48
  end
48
49
 
49
50
  require "uri_service/version"
51
+ require "uri_service/term_type"
50
52
  require "uri_service/client"
51
53
 
52
54
  require 'uri_service/railtie' if defined?(Rails)
@@ -1,18 +1,20 @@
1
1
  class UriService::Client
2
2
 
3
- attr_reader :db, :rsolr_pool, :local_uri_base
3
+ attr_reader :db, :rsolr_pool, :local_uri_base, :temporary_uri_base
4
4
 
5
5
  ALPHANUMERIC_UNDERSCORE_KEY_REGEX = /\A[a-z]+[a-z0-9_]*\z/
6
- VALID_URI_REGEX = /\A#{URI::regexp(['http', 'https'])}\z/
7
- CORE_FIELD_NAMES = ['uri', 'vocabulary_string_key', 'value', 'is_local']
6
+ CORE_FIELD_NAMES = ['uri', 'vocabulary_string_key', 'value', 'type']
7
+ VALID_TYPES = [UriService::TermType::EXTERNAL, UriService::TermType::LOCAL, UriService::TermType::TEMPORARY]
8
8
 
9
9
  def initialize(opts)
10
10
  raise UriService::InvalidOptsError, "Must supply opts['local_uri_base'] to initialize method." if opts['local_uri_base'].nil?
11
+ raise UriService::InvalidOptsError, "Must supply opts['temporary_uri_base'] to initialize method." if opts['temporary_uri_base'].nil?
11
12
  raise UriService::InvalidOptsError, "Must supply opts['database'] to initialize method." if opts['database'].nil?
12
13
  raise UriService::InvalidOptsError, "Must supply opts['solr'] to initialize method." if opts['solr'].nil?
13
14
 
14
- # Set local_uri_base
15
+ # Set local_uri_base and temporary_uri_base
15
16
  @local_uri_base = opts['local_uri_base']
17
+ @temporary_uri_base = opts['temporary_uri_base']
16
18
 
17
19
  # Create DB connection pool
18
20
  @db = Sequel.connect(opts['database'])
@@ -45,7 +47,7 @@ class UriService::Client
45
47
  term_db_row[:value],
46
48
  term_db_row[:uri],
47
49
  JSON.parse(term_db_row[:additional_fields]),
48
- term_db_row[:is_local],
50
+ term_db_row[:type],
49
51
  false)
50
52
 
51
53
  if print_progress_to_console
@@ -122,7 +124,7 @@ class UriService::Client
122
124
  String :uri_hash, fixed: true, size: 64, unique: true
123
125
  String :value, text: true
124
126
  String :value_hash, fixed: true, size: 64
125
- TrueClass :is_local, default: false
127
+ String :type, null: false
126
128
  String :additional_fields, text: true
127
129
  end
128
130
  puts 'Created table: ' + UriService::TERMS.to_s
@@ -157,79 +159,60 @@ class UriService::Client
157
159
  end
158
160
  end
159
161
 
160
- # Creates a new term.
161
- def create_term(vocabulary_string_key, value, term_uri, additional_fields={})
162
- return self.create_term_impl(vocabulary_string_key, value, term_uri, additional_fields, false, false)
163
- end
164
-
165
- # Creates a new local term, auto-generating a URI
166
- # By default, if raise_error_if_local_term_value_exists_in_vocabulary param is true, rejects the creation of a local value if that exact value already exists in the specified vocabulary
167
- def create_local_term(vocabulary_string_key, value, additional_fields={}, raise_error_if_local_term_value_exists_in_vocabulary=true)
168
-
169
- # Create a new URI for this local term, using the @local_uri_base
170
- term_uri = URI(@local_uri_base)
171
- term_uri.path += SecureRandom.uuid # Generate random UUID for local URI
172
- term_uri = term_uri.to_s
162
+ # Creates a new term
163
+ def create_term(type, opts)
164
+ raise UriService::InvalidTermTypeError, 'Invalid type: ' + type unless VALID_TYPES.include?(type)
173
165
 
174
- # Getting a duplicate UUID from SecureRandom.uuid is EXTREMELY unlikely, but we'll account for it just in case (by making a few more attempts).
175
- 5.times {
176
- begin
177
- return self.create_term_impl(vocabulary_string_key, value, term_uri, additional_fields, true, raise_error_if_local_term_value_exists_in_vocabulary)
178
- rescue UriService::ExistingUriError
179
- raise UriService::ExistingUriError, "UriService generated a duplicate random UUID (via SecureRandom.uuid) and will now attempt to create another. This type of problem is EXTREMELY rare."
180
- end
181
- }
182
-
183
- return nil
184
- end
185
-
186
- def create_term_impl(vocabulary_string_key, value, term_uri, additional_fields, is_local, raise_error_if_local_term_value_exists_in_vocabulary)
187
- self.handle_database_disconnect do
166
+ vocabulary_string_key = opts.delete(:vocabulary_string_key)
167
+ value = opts.delete(:value)
168
+ uri = opts.delete(:uri)
169
+ additional_fields = opts.delete(:additional_fields) || {}
188
170
 
189
- additional_fields.stringify_keys!
171
+ if type == UriService::TermType::EXTERNAL
172
+ # URI is required
173
+ raise UriService::InvalidOptsError, "A uri must be supplied for terms of type #{type}." if uri.nil?
190
174
 
191
- #Ensure that vocabulary with vocabulary_string_key exists
192
- if self.find_vocabulary(vocabulary_string_key).nil?
193
- raise UriService::NonExistentVocabularyError, "There is no vocabulary with string key: " + vocabulary_string_key
194
- end
195
- unless term_uri =~ VALID_URI_REGEX
196
- raise UriService::InvalidUriError, "Invalid URI supplied: #{term_uri}, with result #{(VALID_URI_REGEX.match(term_uri)).to_s}"
197
- end
198
- validate_additional_fields(additional_fields) # This method call raises an error if an invalid additional_field key is supplied
175
+ return create_term_impl(type, vocabulary_string_key, value, uri, additional_fields)
176
+ else
177
+ # URI should not be present
178
+ raise UriService::InvalidOptsError, "A uri cannot supplied for term type: #{type}." unless uri.nil?
199
179
 
200
- @db.transaction do
201
-
202
- value_hash = Digest::SHA256.hexdigest(value)
203
-
204
- if raise_error_if_local_term_value_exists_in_vocabulary && @db[UriService::TERMS].where(value_hash: value_hash, vocabulary_string_key: vocabulary_string_key).count > 0
205
- raise UriService::DisallowedDuplicateLocalTermValueError, "A local term already exists with the value #{value}. This is not allowed when param raise_error_if_local_term_value_exists_in_vocabulary == true."
206
- end
207
-
208
- begin
209
- @db[UriService::TERMS].insert(
210
- is_local: is_local,
211
- uri: term_uri,
212
- uri_hash: Digest::SHA256.hexdigest(term_uri),
213
- value: value,
214
- value_hash: value_hash,
215
- vocabulary_string_key: vocabulary_string_key,
216
- additional_fields: JSON.generate(additional_fields)
217
- )
218
- send_term_to_solr(vocabulary_string_key, value, term_uri, additional_fields, is_local)
219
- rescue Sequel::UniqueConstraintViolation
220
- raise UriService::ExistingUriError, "A term already exists with uri: " + term_uri + " (conflict found via uri_hash check)"
221
- end
180
+ if type == UriService::TermType::TEMPORARY
181
+ # No two TEMPORARY terms within the same vocabulary can have the same value, so we generate a unique URI from a hash of the (vocabulary_string_key + value) to ensure uniqueness.
182
+ uri = self.generate_uri_for_temporary_term(vocabulary_string_key, value)
183
+ return create_term_impl(type, vocabulary_string_key, value, uri, additional_fields)
184
+ elsif type == UriService::TermType::LOCAL
185
+ 5.times {
186
+ # We generate a unique URI for a local term from a UUID generator.
187
+ # Getting a duplicate UUID from a call to SecureRandom.uuid is EXTREMELY unlikely,
188
+ # but we'll account for it just in case by being ready to make multiple attempts.
189
+ begin
190
+ # Generate new URI for LOCAL and TEMPORARY terms
191
+ uri = URI(@local_uri_base)
192
+ uri.path += SecureRandom.uuid # Generate random UUID for local URI
193
+ uri = uri.to_s
194
+ return create_term_impl(type, vocabulary_string_key, value, uri, additional_fields)
195
+ rescue UriService::ExistingUriError
196
+ next
197
+ end
198
+ }
199
+ # Probabilistically, the error below should never be raised.
200
+ raise UriService::CouldNotGenerateUriError, "UriService generated a duplicate random UUID (via SecureRandom.uuid) too many times in a row. Probabilistically, this should never happen."
222
201
  end
223
202
 
224
- return generate_frozen_term_hash(vocabulary_string_key, value, term_uri, additional_fields, is_local)
225
203
  end
226
204
  end
227
205
 
228
- def generate_frozen_term_hash(vocabulary_string_key, value, uri, additional_fields, is_local)
206
+ def generate_uri_for_temporary_term(vocabulary_string_key, term_value)
207
+ uri = URI(@temporary_uri_base + Digest::SHA256.hexdigest(vocabulary_string_key + term_value))
208
+ return uri.to_s
209
+ end
210
+
211
+ def generate_frozen_term_hash(vocabulary_string_key, value, uri, additional_fields, type)
229
212
  hash_to_return = {}
230
213
  hash_to_return['uri'] = uri
231
214
  hash_to_return['value'] = value
232
- hash_to_return['is_local'] = is_local
215
+ hash_to_return['type'] = type
233
216
  hash_to_return['vocabulary_string_key'] = vocabulary_string_key
234
217
 
235
218
  additional_fields.each do |key, val|
@@ -241,45 +224,43 @@ class UriService::Client
241
224
  return hash_to_return
242
225
  end
243
226
 
244
- def create_term_solr_doc(vocabulary_string_key, value, uri, additional_fields, is_local)
227
+ def create_term_solr_doc(vocabulary_string_key, value, uri, additional_fields, type)
245
228
  doc = {}
246
229
  doc['uri'] = uri
247
230
  doc['value'] = value
248
- doc['is_local'] = is_local
231
+ doc['type'] = type
249
232
  doc['vocabulary_string_key'] = vocabulary_string_key
250
233
 
251
- additional_fields.each do |key, val|
252
- doc[key + self.class.get_solr_suffix_for_object(val)] = val
253
- end
234
+ doc['additional_fields'] = JSON.generate(additional_fields)
254
235
 
255
236
  return doc
256
237
  end
257
238
 
258
- def self.get_solr_suffix_for_object(obj)
259
- if obj.is_a?(Array)
260
- # Note boolean arrays aren't supported because they don't seem useful in this context
261
- if obj[0].is_a?(Fixnum)
262
- return '_isim'
263
- else
264
- # Treat like a string array
265
- return '_ssim'
266
- end
267
- else
268
- if obj.is_a?(String)
269
- return '_ssi'
270
- elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
271
- return '_bsi'
272
- elsif obj.is_a?(Fixnum)
273
- return '_isi'
274
- else
275
- raise UriService::UnsupportedObjectTypeError, "Unable to determine solr suffix for unsupported object type: #{obj.class.name}"
276
- end
277
- end
278
- end
239
+ #def self.get_solr_suffix_for_object(obj)
240
+ # if obj.is_a?(Array)
241
+ # # Note boolean arrays aren't supported because they don't seem useful in this context
242
+ # if obj[0].is_a?(Fixnum)
243
+ # return '_isim'
244
+ # else
245
+ # # Treat like a string array
246
+ # return '_ssim'
247
+ # end
248
+ # else
249
+ # if obj.is_a?(String)
250
+ # return '_ssi'
251
+ # elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
252
+ # return '_bsi'
253
+ # elsif obj.is_a?(Fixnum)
254
+ # return '_isi'
255
+ # else
256
+ # raise UriService::UnsupportedObjectTypeError, "Unable to determine solr suffix for unsupported object type: #{obj.class.name}"
257
+ # end
258
+ # end
259
+ #end
279
260
 
280
261
  # Index the DB row term data into solr
281
- def send_term_to_solr(vocabulary_string_key, value, term_uri, additional_fields, is_local, commit=true)
282
- doc = create_term_solr_doc(vocabulary_string_key, value, term_uri, additional_fields, is_local)
262
+ def send_term_to_solr(vocabulary_string_key, value, uri, additional_fields, type, commit=true)
263
+ doc = create_term_solr_doc(vocabulary_string_key, value, uri, additional_fields, type)
283
264
  @rsolr_pool.with do |rsolr|
284
265
  rsolr.add(doc)
285
266
  rsolr.commit if commit
@@ -287,7 +268,7 @@ class UriService::Client
287
268
  end
288
269
 
289
270
  # Validates additional_fields and verifies that no reserved words are supplied
290
- def validate_additional_fields(additional_fields)
271
+ def validate_additional_field_keys(additional_fields)
291
272
  additional_fields.each do |key, value|
292
273
  if CORE_FIELD_NAMES.include?(key.to_s)
293
274
  raise UriService::InvalidAdditionalFieldKeyError, "Cannot supply the key \"#{key.to_s}\" as an additional field because it is a reserved key."
@@ -308,8 +289,14 @@ class UriService::Client
308
289
  end
309
290
  end
310
291
 
311
- # Finds the first term matching the specified conditions
312
- def find_term_by(opts)
292
+ # Finds the term with the given uri
293
+ def find_term_by_uri(uri)
294
+ results = self.find_terms_where({uri: uri}, 1)
295
+ return results.length == 1 ? results.first : nil
296
+ end
297
+
298
+ # Finds terms that match the specified conditions
299
+ def find_terms_where(opts, limit=10)
313
300
  fqs = []
314
301
 
315
302
  # Only search on allowed fields
@@ -324,14 +311,20 @@ class UriService::Client
324
311
  response = rsolr.get('select', params: {
325
312
  :q => '*:*',
326
313
  :fq => fqs,
327
- :rows => 1,
328
- :sort => 'score desc, value_ssort asc, uri asc' # For consistent sorting
314
+ :rows => limit,
315
+ :sort => 'value_ssort asc, uri asc' # For consistent sorting
316
+ # Note: We don't sort by solr score because solr fq searches don't factor into the score
329
317
  })
330
- if response['response']['numFound'] == 1
331
- return term_solr_doc_to_frozen_term_hash(response['response']['docs'].first)
318
+ if response['response']['docs'].length > 0
319
+ arr_to_return = []
320
+ response['response']['docs'].each do |doc|
321
+ arr_to_return << term_solr_doc_to_frozen_term_hash(doc)
322
+ end
323
+ return arr_to_return
324
+ else
325
+ return []
332
326
  end
333
327
  end
334
- return nil
335
328
  end
336
329
 
337
330
  def term_solr_doc_to_frozen_term_hash(term_solr_doc)
@@ -339,19 +332,10 @@ class UriService::Client
339
332
  uri = term_solr_doc.delete('uri')
340
333
  vocabulary_string_key = term_solr_doc.delete('vocabulary_string_key')
341
334
  value = term_solr_doc.delete('value')
342
- is_local = term_solr_doc.delete('is_local')
343
- additional_fields = {}
344
-
345
- # Iterate through remaining keys and put them in additional_fields after removing suffixes
346
- term_solr_doc.each do |key, val|
347
- # Skip certain automatically added fields that aren't part of the term_hash
348
- next if ['_version_', 'timestamp', 'score'].include?(key)
349
-
350
- # Remove trailing '_si', '_bi', etc. if present for solr-suffixed fields
351
- additional_fields[key.gsub(/_[^_]+$/, '')] = val
352
- end
335
+ type = term_solr_doc.delete('type')
336
+ additional_fields = JSON.parse(term_solr_doc.delete('additional_fields'))
353
337
 
354
- return generate_frozen_term_hash(vocabulary_string_key, value, uri, additional_fields, is_local)
338
+ return generate_frozen_term_hash(vocabulary_string_key, value, uri, additional_fields, type)
355
339
  end
356
340
 
357
341
  def find_terms_by_query(vocabulary_string_key, value_query, limit=10, start=0)
@@ -433,12 +417,12 @@ class UriService::Client
433
417
  end
434
418
  end
435
419
 
436
- def delete_term(term_uri, commit=true)
420
+ def delete_term(uri, commit=true)
437
421
  self.handle_database_disconnect do
438
422
  @db.transaction do
439
- @db[UriService::TERMS].where(uri: term_uri).delete
423
+ @db[UriService::TERMS].where(uri: uri).delete
440
424
  @rsolr_pool.with do |rsolr|
441
- rsolr.delete_by_query('uri:' + UriService.solr_escape(term_uri))
425
+ rsolr.delete_by_query('uri:' + UriService.solr_escape(uri))
442
426
  rsolr.commit if commit
443
427
  end
444
428
  end
@@ -461,12 +445,15 @@ class UriService::Client
461
445
  end
462
446
 
463
447
  # opts format: {:value => 'new value', :additional_fields => {'key' => 'value'}}
464
- def update_term(term_uri, opts, merge_additional_fields=true)
448
+ def update_term(uri, opts, merge_additional_fields=true)
465
449
  self.handle_database_disconnect do
466
- dataset = @db[UriService::TERMS].where(uri: term_uri)
467
- raise UriService::NonExistentUriError, "No term found with uri: " + term_uri if dataset.count == 0
468
-
469
- term_db_row = dataset.first
450
+ term_db_row = @db[UriService::TERMS].first(uri: uri)
451
+ raise UriService::NonExistentUriError, "No term found with uri: " + uri if term_db_row.nil?
452
+
453
+ if term_db_row[:type] == UriService::TermType::TEMPORARY
454
+ # TEMPORARY terms cannot have their values, additional_fields or anything else changed
455
+ raise UriService::CannotChangeTemporaryTerm, "Temporary terms cannot be changed. Delete unusued temporary terms or create new ones."
456
+ end
470
457
 
471
458
  new_value = opts[:value] || term_db_row[:value]
472
459
  new_additional_fields = term_db_row[:additional_fields].nil? ? {} : JSON.parse(term_db_row[:additional_fields])
@@ -479,14 +466,14 @@ class UriService::Client
479
466
  new_additional_fields = opts[:additional_fields]
480
467
  end
481
468
  end
482
- validate_additional_fields(new_additional_fields)
469
+ validate_additional_field_keys(new_additional_fields)
483
470
 
484
471
  @db.transaction do
485
- dataset.update(value: new_value, value_hash: Digest::SHA256.hexdigest(new_value), additional_fields: JSON.generate(new_additional_fields))
486
- self.send_term_to_solr(term_db_row[:vocabulary_string_key], new_value, term_uri, new_additional_fields, term_db_row[:is_local])
472
+ @db[UriService::TERMS].where(uri: uri).update(value: new_value, value_hash: Digest::SHA256.hexdigest(new_value), additional_fields: JSON.generate(new_additional_fields))
473
+ self.send_term_to_solr(term_db_row[:vocabulary_string_key], new_value, uri, new_additional_fields, term_db_row[:type])
487
474
  end
488
475
 
489
- return generate_frozen_term_hash(term_db_row[:vocabulary_string_key], new_value, term_uri, new_additional_fields, term_db_row[:is_local])
476
+ return generate_frozen_term_hash(term_db_row[:vocabulary_string_key], new_value, uri, new_additional_fields, term_db_row[:type])
490
477
  end
491
478
  end
492
479
 
@@ -513,16 +500,95 @@ class UriService::Client
513
500
  end
514
501
  end
515
502
 
503
+ #########################
504
+ # BEGIN PRIVATE METHODS #
505
+ #########################
506
+
507
+ private
508
+
509
+ # Backing implementation for actual term creation in db/solr.
510
+ # - Performs some data validations.
511
+ # - Ensures uniqueness of URIs in database.
512
+ # - Returns an existing TEMPORARY term if a user attempts to
513
+ # create a new TEMPORARY term with an existing value/vocabulary combo.
514
+ def create_term_impl(type, vocabulary_string_key, value, uri, additional_fields)
515
+ self.handle_database_disconnect do
516
+
517
+ if type == UriService::TermType::TEMPORARY
518
+ # If this is a TEMPORARY term, we need to ensure that the temporary
519
+ # passed in URI is a hash of the vocabulary + value, just in case this
520
+ # method is ever called directly instead of through the create_term
521
+ # wrapper method. This is to ensure that our expectations about the
522
+ # uniqueness of TEMPORARY term values is never violated.
523
+ unless uri == self.generate_uri_for_temporary_term(vocabulary_string_key, value)
524
+ raise UriService::InvalidTemporaryTermUriError, "The supplied URI was not derived from the supplied (vocabulary_string_key+value) pair."
525
+ end
526
+
527
+ # TEMPORARY terms are not meant to hold data in additional_fields.
528
+ if additional_fields.size > 0
529
+ raise UriService::InvalidOptsError, "Terms of type #{type} cannot have additional_fields."
530
+ end
531
+ end
532
+
533
+ unless uri =~ UriService::VALID_URI_REGEX
534
+ raise UriService::InvalidUriError, "Invalid URI supplied during term creation: #{uri}"
535
+ end
536
+
537
+ #Ensure that vocabulary with vocabulary_string_key exists
538
+ if self.find_vocabulary(vocabulary_string_key).nil?
539
+ raise UriService::NonExistentVocabularyError, "There is no vocabulary with string key: " + vocabulary_string_key
540
+ end
541
+
542
+ # Stringify and validate keys for additional_fields
543
+ additional_fields.stringify_keys!
544
+ validate_additional_field_keys(additional_fields) # This method call raises an error if an invalid additional_field key is supplied
545
+
546
+ @db.transaction do
547
+ value_hash = Digest::SHA256.hexdigest(value)
548
+
549
+ begin
550
+ @db[UriService::TERMS].insert(
551
+ type: type,
552
+ uri: uri,
553
+ uri_hash: Digest::SHA256.hexdigest(uri),
554
+ value: value,
555
+ value_hash: value_hash,
556
+ vocabulary_string_key: vocabulary_string_key,
557
+ additional_fields: JSON.generate(additional_fields)
558
+ )
559
+ send_term_to_solr(vocabulary_string_key, value, uri, additional_fields, type)
560
+ rescue Sequel::UniqueConstraintViolation
561
+
562
+ # If this is a new TEMPORARY term and we ran into a Sequel::UniqueConstraintViolation,
563
+ # that mean that the term already exists. We should return that existing term.
564
+ # don't create a new one. Instead, return the existing one.
565
+ if type == UriService::TermType::TEMPORARY
566
+ return self.find_term_by_uri(uri)
567
+ end
568
+
569
+ raise UriService::ExistingUriError, "A term already exists with uri: " + uri + " (conflict found via uri_hash check)"
570
+
571
+ end
572
+
573
+ return generate_frozen_term_hash(vocabulary_string_key, value, uri, additional_fields, type)
574
+
575
+ end
576
+ end
577
+ end
578
+
516
579
  end
517
580
 
581
+ class UriService::CannotChangeTemporaryTerm < StandardError;end
582
+ class UriService::CouldNotGenerateUriError < StandardError;end
518
583
  class UriService::InvalidAdditionalFieldKeyError < StandardError;end
519
- class UriService::InvalidVocabularyStringKeyError < StandardError;end
520
584
  class UriService::InvalidOptsError < StandardError;end
585
+ class UriService::InvalidTemporaryTermUriError < StandardError;end
586
+ class UriService::InvalidTermTypeError < StandardError;end
521
587
  class UriService::InvalidUriError < StandardError;end
588
+ class UriService::InvalidVocabularyStringKeyError < StandardError;end
522
589
  class UriService::ExistingUriError < StandardError;end
523
590
  class UriService::ExistingVocabularyStringKeyError < StandardError;end
524
591
  class UriService::NonExistentUriError < StandardError;end
525
592
  class UriService::NonExistentVocabularyError < StandardError;end
526
593
  class UriService::UnsupportedObjectTypeError < StandardError;end
527
- class UriService::UnsupportedSearchFieldError < StandardError;end
528
- class UriService::DisallowedDuplicateLocalTermValueError < StandardError;end
594
+ class UriService::UnsupportedSearchFieldError < StandardError;end
@@ -0,0 +1,7 @@
1
+ module UriService
2
+ module TermType
3
+ EXTERNAL = 'external'
4
+ LOCAL = 'local'
5
+ TEMPORARY = 'temporary'
6
+ end
7
+ end
@@ -1,6 +1,6 @@
1
1
  module UriService
2
2
 
3
- VERSION = '0.2.12'
3
+ VERSION = '0.3.0'
4
4
 
5
5
  def self.version
6
6
  VERSION
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uri_service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric O'Hanlon
@@ -179,6 +179,7 @@ files:
179
179
  - lib/uri_service.rb
180
180
  - lib/uri_service/client.rb
181
181
  - lib/uri_service/railtie.rb
182
+ - lib/uri_service/term_type.rb
182
183
  - lib/uri_service/version.rb
183
184
  homepage: https://github.com/cul/uri_service
184
185
  licenses:
@@ -206,4 +207,3 @@ specification_version: 4
206
207
  summary: A service for registering local URIs and performing both local and remote
207
208
  URI lookups.
208
209
  test_files: []
209
- has_rdoc: