Class: Referent

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
MetadataHelper
Defined in:
app/models/referent.rb

Constant Summary

@@shortcut_attributes =

Shortcuts are really used as retrieval keys to 'shortcut' matching referent. They hold normalized value (use ReferentValue.normalize) or empty string. Never nil.

[:atitle, :title, :issn, :isbn, :volume, :year]

Class Method Summary (collapse)

Instance Method Summary (collapse)

Class Method Details

+ (Object) clean_up_context_object(co)

Okay, we need to do some pre-processing on weird context objects sent by, for example, firstSearch. Remove invalid identifiers. Also will adjust context objects according to configured umlaut refernet filters (see config.app_config.referent_filters in environment.rb ) Mutator: Modifies ContextObject arg passed in.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'app/models/referent.rb', line 132

def self.clean_up_context_object(co)
  # First, remove any empty DOIs! or other empty identifiers?
  # LOTS of sources send awful empty identifiers. 
  # That's not a valid identifier!
  empty_ids = co.referent.identifiers.find_all { |i| i =~ Regexp.new('^[^:]+:[^/:]*(/|:)?$')}
  empty_ids.each { |e| co.referent.delete_identifier( e )}
  
  # Now look for ISSN identifiers that are on article_level. FirstSearch
  # gives us ISSN identifiers incorrectly on article level cites. 
  issn_ids = co.referent.identifiers.find_all { |i| i =~ /^urn:ISSN/}
  issn_ids.each do |issn_id|
    # Long as we're at it, add an rft.issn if one's not there.
    issn_data = issn_id.slice( (9..issn_id.length)) # actual ISSN without identifier prefix
    co.referent.(issn, issn_data) if co.referent.('issn').blank? && ! issn_data.blank?

    # And remove it as an identifier unless we know this is journal-level
    # cite.
    unless ( co.referent.('genre') == 'journal' )
      co.referent.delete_identifier( issn_id )
    end      
  end

  # Clean up OCLC numbers from old bad formats that may have snuck in to an info url incorrectly. # also delete preceding 0's
  oclcnum_ids = co.referent.identifiers.find_all { |i| i =~ /^info:oclcnum/}
  oclcnum_ids.each do |oclcnum_id|
    # FIXME Does this regex need "ocn" as well?
    if (oclcnum_id =~ /^info:oclcnum\/(ocm0*|ocn0*|\(OCoLC\)0*|ocl70*|0+)(.*)$/)
      # Delete the original, take out just the actual oclcnum, not
      # those old prefixes. or preceding 0s.
      co.referent.delete_identifier( oclcnum_id )
      co.referent.add_identifier("info:oclcnum/#{$2}")
    end
  end


  
  
end

+ (Object) create_by_context_object(co, referrer, options = {})

Does call save! on referent created. :permalink => false if you already have a permalink and don't need to create one. Caller should attach that permalink to this referent!



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'app/models/referent.rb', line 87

def self.create_by_context_object(co, referrer, options = {})    
  
  self.clean_up_context_object(co)    
  rft = Referent.new

  # Wrap everything in a transaction for better efficiency, at least
  # with MySQL, I think. 
  
  Referent.transaction do
    
    rft.set_values_from_context_object(co)

    unless ( options[:permalink] == false)
      permalink = Permalink.new_with_values!(rft, referrer)            
    end

    # Add shortcuts.
    rft.referent_values.each do | val |
      rft.atitle = val.normalized_value if val.key_name == 'atitle' and val.metadata?
      rft.title = val.normalized_value if val.key_name.match(/^[bj]?title$/) and val.metadata? 
      rft.issn = val.normalized_value if val.key_name == 'issn' and val.metadata?
      rft.isbn = val.normalized_value if val.key_name == 'isbn' and val.metadata?      
      rft.volume = val.normalized_value if val.key_name == 'volume' and val.metadata?
      rft.year = val.normalized_value if val.key_name == 'date' and val.metadata?
    end
    rft.save!

    # Apply referent filters
    rfr_id = referrer ? referrer.identifier : ''
    rfr_id = '' if rfr_id.nil?
    AppConfig.param("referent_filters").each do |regexp, filter|
      if (regexp =~ rfr_id)
        filter.filter(rft) if filter.respond_to?(:filter)
      end
    end
  end
  return rft          
end

+ (Object) find_by_context_object(co)

When provided an OpenURL::ContextObject, it will return a Referent object (if one exists). At least that's the intent. This turns out to be a really tricky task, identifying when two citations that may not match exactly are the same citation. So this doesn't really work well--we err on the side of missing existing matches, better than finding a false match. So there are seldom matches found. A particular problem is that when the Referent is enhanced by a service, it will no longer match itself as it came in! Oh well.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'app/models/referent.rb', line 29

def self.find_by_context_object(co)
  
  rft = co.referent

      
  # Try to find for re-use by special indexed shortcut values. Create hash
  # of shortcuts. 
  
  # Preload values as empty, even if they aren't found in our
  # incoming referent--we want to find a match with them empty too, then! 
  shortcuts = {:atitle=>"", :title=>"", :issn=>"", :isbn=>"", :volume=>"", :year=>""}
  
  # Special handling of title
  incoming_title = rft.['jtitle'] || rft.['btitle'] || rft.['title']
  # DC OpenURL is an array, not a single value. Grr. 
  incoming_title = incoming_title[0] if incoming_title.kind_of?(Array)
  shortcuts[:title] = ReferentValue.normalize(incoming_title) if incoming_title
  # Special handling of date/year, since we use year instead of date for
  # stored shortcut. 
  # I don't know why.
  shortcuts[:year] = rft.['date'] if rft.['date']

  # Other four. 
  [:atitle, :issn, :isbn, :volume].each do |att|
    shortcuts[att] = ReferentValue.normalize( rft.[att.to_s]) if rft.[ att.to_s ]
  end
  # Don't look up by shortcuts if they're ALL blank. That doesn't do us well.
  found_rft = nil
  found_rft = Referent.find(:first, :conditions => shortcuts) if  shortcuts.values.find {|v| ! v.empty?}
  if ( found_rft && found_rft.( rft ) )
    return found_rft
  end
  
  # found nothing?
  return nil
end

+ (Object) find_or_create_by_context_object(co, referrer)

When provided an OpenURL::ContextObject, it will return a Referent object (creating one if doesn't already exist) . At least that's the idea. But see caveats at #find_by_context_object . Most of the time this ends up creating a new Referent. pass in referrer for source-specific referent munging.



71
72
73
74
75
76
77
78
79
80
81
82
# File 'app/models/referent.rb', line 71

def self.find_or_create_by_context_object(co, referrer)
  # Okay, we need to do some pre-processing on weird context objects
  # sent by, for example, firstSearch.
  self.clean_up_context_object(co)
  
  if rft = Referent.find_by_context_object(co) 
    return rft
  else
    rft = Referent.create_by_context_object(co, referrer)
    return rft
  end
end

Instance Method Details

- (Object) add_identifier(id)



272
273
274
275
276
# File 'app/models/referent.rb', line 272

def add_identifier(id)
  unless ( identifiers.find{|i| i == id}  )
    self.referent_values.create(:key_name => 'identifier', :value => id, :normalized_value => ReferentValue.normalize(id), :metadata => false, :private_data => false).save!            
  end
end

- (Object) before_validation_on_create



13
14
15
16
17
18
# File 'app/models/referent.rb', line 13

def before_validation_on_create
  # shortcuts initialize to empty string, they should never be null.
  @@shortcut_attributes.each do |key|
    self[key] = "" if self[key].nil?
  end
end

- (Object) enhance_referent(key, value, metadata = true, private_data = false, options = {})

options => { :overwrite => false } to only enhance if not already there



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'app/models/referent.rb', line 440

def enhance_referent(key, value, =true, private_data=false, options = {})
  return if value.nil?

  matches = self.referent_values.to_a.find_all do |rv| 
    (rv.key_name == key) && (rv. == ) && (rv.private_data == private_data) 
  end
  
  matches.each do |rv|
    unless (options[:overwrite] == false || rv.value == value)
      rv.value = value
      rv.save!
    end
  end
  
  if (matches.length == 0)
    val = self.referent_values.create(:key_name => key, :value => value, :normalized_value => ReferentValue.normalize(value), :metadata => , :private_data => private_data)
    val.save!
  end
  
  if key.match((/(^[ajb]?title$)|(^is[sb]n$)|(^volume$)|(^date$)/))
    case key
      when 'date' then self.year = ReferentValue.normalize(value)
      when 'volume' then self.volume = ReferentValue.normalize(value)
      when 'issn' then self.issn = ReferentValue.normalize(value)
      when 'isbn' then self.isbn = ReferentValue.normalize(value)
      when 'atitle' then self.atitle = ReferentValue.normalize(value)
      else self.title = ReferentValue.normalize(value)
    end
    self.save!
  end
end

- (Object) ensure_value!(key_name, value)

Find or create a ReferentValue object hanging off this Referent, with given key name and value. key_name can be 'identifier', 'format', or any metadata key.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'app/models/referent.rb', line 175

def ensure_value!(key_name, value)
   normalized_value = ReferentValue.normalize(value)
   
   rv = ReferentValue.find(:first, 
                     :conditions => { :referent_id => self.id,
                                      :key_name => key_name,
                                      :normalized_value => normalized_value })
    unless (rv)
      rv = ReferentValue.new
      rv.referent = self
      
      rv.key_name = key_name
      rv.value = value
      rv.normalized_value = normalized_value

      unless (key_name == "identifier" || key_name == "format")
        rv. = true
      end

      rv.save!
    end
    return rv
end

- (Object) format



278
279
280
281
282
283
284
285
# File 'app/models/referent.rb', line 278

def format
  self.referent_values
  self.referent_values.each { | val |    
    if val.key_name == 'format'
      return val.value
    end
  }    
end

- (Object) identifiers



261
262
263
264
265
266
267
268
269
270
# File 'app/models/referent.rb', line 261

def identifiers
  self.referent_values
  identifiers = []
  self.referent_values.each { | val |    
    if val.key_name == 'identifier'
      identifiers << val.value
    end
  }
  return identifiers
end

- (Object) isbn



304
305
306
# File 'app/models/referent.rb', line 304

def isbn
  return get_isbn(self)
end

- (Object) issn

Gets an ISSN, makes sure it's a valid ISSN or else returns nil. So will return a valid ISSN (NOT empty string) or nil.



300
301
302
# File 'app/models/referent.rb', line 300

def issn
  return get_issn(self)
end

- (Object) lccn

finds and normalizes an LCCN. If multiple LCCNs are in the record, returns the first one. Returns a NORMALIZED lccn, but does NOT do validation. see: info-uri.info/registry/OAIHandler?verb=GetRecord&metadataPrefix=reg&identifier=info:lccn/



294
295
296
# File 'app/models/referent.rb', line 294

def lccn
  return get_lccn(self)
end

- (Object) metadata

Creates a hash of values from referrent_values, to assemble what was spread accross differnet db rows into one easy-lookup hash, for easy access. See also #to_citation for a different hash, specifically for use in View to print citation. And #to_context_object.



244
245
246
247
248
249
250
# File 'app/models/referent.rb', line 244

def 
   = {}
  self.referent_values.each { | val |
    [val.key_name] = val.value if val.metadata? and not val.private_data?
  }
  return 
end

- (Boolean) metadata_intersects?(arg)

pass in a Referent, or a ropenurl ContextObjectEntity that has a metadata method. Or really anything with a #metadata method returning openurl-style keys and values. Method returns true iff the keys in common to both metadata packages have equal (==) values.

Returns:

  • (Boolean)


224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'app/models/referent.rb', line 224

def (arg)
  
  # if it's empty, good enough. 
  return true unless arg
  
  intersect_keys = self..keys & arg..keys
  # Take out keys who's values are blank. If one is blank but not
  # both, we can still consider that a match. 
  intersect_keys.delete_if{ |k| self.[k].blank? || arg.[k].blank? }
  
  self_subset = self..reject{ |k, v| ! intersect_keys.include?(k) }
  arg_subset = arg..reject{ |k, v| ! intersect_keys.include?(k) }

  return self_subset == arg_subset    
end

- (Object) oclcnum



308
309
310
# File 'app/models/referent.rb', line 308

def oclcnum
  return get_oclcnum(self)
end

- (Object) private_data



252
253
254
255
256
257
258
259
# File 'app/models/referent.rb', line 252

def private_data
  self.referent_values
  priv_data = {}
  self.referent_values.each { | val |
    priv_data[val.key_name] = val.value if val.private_data?
  }
  return priv_data    
end

- (Object) remove_value(key)



433
434
435
436
437
# File 'app/models/referent.rb', line 433

def remove_value(key)
  referent_values.find(:all, :conditions=> ['key_name =?', key]).each do |rv|
    referent_values.delete(rv)
  end    
end

- (Object) set_values_from_context_object(co)

Populate the referent_values table with a ropenurl contextobject object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/referent.rb', line 200

def set_values_from_context_object(co)
  
  rft = co.referent


  # Multiple identifiers are possible! 
  rft.identifiers.each do |id_string|
    ensure_value!('identifier', id_string)            
  end
  if rft.format
    ensure_value!('format', rft.format)
  end
                    
  rft..each { | key, value |
    next unless value
    ensure_value!( key, value)      
  }    
end

- (Object) to_citation

Creates a hash for use in View code to display a citation



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'app/models/referent.rb', line 336

def to_citation
  citation = {}
  # call self.metadata once and use the array for efficiency, don't
  # keep calling it. profiling shows it DOES make a difference. 
   = self.
  
  
  if ['atitle']
    citation[:title] = ['atitle']
    citation[:title_label], citation[:subtitle_label] = 
      case ['genre']
        when /article|journal|issue/ then ['Article Title', 'Journal Title']
        when /bookitem|book/ then ['Chapter/Part Title', 'Book Title']
        when /proceeding|conference/ then ['Proceeding Title', 'Conference Name']
        when 'report' then ['Report Title','Report']    
        else
        if self.format == 'book'
          ['Chapter/Part Title', 'Title']
        elsif self.format == 'journal'
          ['Article Title', 'Journal Title']
        else # default fall through, use much what SFX uses. 
          ['Title', 'Source']
        end
      end
    ['title','btitle','jtitle'].each do | t_type |
      if ! [t_type].blank?
        citation[:subtitle] = [t_type]
        citation[:container_title] = [t_type]
        break
      end
    end
  else      
    citation[:title_label] = case ["genre"]
      when /article|journal|issue/ then 'Journal Title'
      when /bookitem|book/ then 'Book Title'
      when /proceeding|conference/ then 'Conference Name'
      when 'report' then 'Report Title'
      else'Title'
    end
    ['title','btitle','jtitle'].each do | t_type |
      if ! [t_type].blank?
        citation[:title] = [t_type]
        break
      end
    end      
  end
  # add publisher for books
  if (['genre'] == 'book')
    citation[:pub] = ['pub'] unless ['pub'].blank?
  end

  citation[:issn] = issn if issn
  citation[:isbn] = isbn if isbn
  
  ['volume','issue','date'].each do | key |
    citation[key.to_sym] = [key]
  end
  if ! ["au"].blank?
    citation[:author] = ["au"]
  elsif  ["aulast"]
    citation[:author] = ["aulast"]
    if ! ["aufirst"].blank?
 		citation[:author] += ',	'+["aufirst"]
    else
      if ! ["auinit"].blank?
        citation[:author] += ',	'+["auinit"]
      else
  if ! ["auinit1"].blank?
          citation[:author] += ',	'+["auinit1"]
 		  end
     	  if ! ["auinitm"].blank?
          citation[:author] += ["auinitm"]
 		  end
 	    end
 	  end
 	end 
 	if ['spage']
 	  citation[:page] = ['spage']
 	  citation[:page] += ' - ' + ['epage'] if ! ['epage'].blank?
 	end
 	citation[:identifiers] = []
 	self.identifiers.each do | id |
 	  citation[:identifiers] << id unless (id.blank? || id.match(/^tag:/))
 	end
 	return citation
end

- (Object) to_context_object

Creates an OpenURL::ContextObject assembling all the data in this referrent.



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'app/models/referent.rb', line 314

def to_context_object
  co = OpenURL::ContextObject.new

  # Got to initialize the referent entity properly for our format.
  # OpenURL sucks, this is confusing, yes. 
  fmt_uri = 'info:ofi/fmt:xml:xsd:' + self.format
  co.referent = OpenURL::ContextObjectEntity.new_from_format( fmt_uri )
  rft = co.referent
  
  # Now set all the values. 
  self.referent_values.each do | val |
    next if val.private_data?
    if val.metadata?
      rft.(val.key_name, val.value)
      next
    end
    rft.send('set_'+val.key_name, val.value) if rft.respond_to?('set_'+val.key_name)        
  end
  return co
end

- (Object) type_of_thing



423
424
425
426
427
428
429
430
431
# File 'app/models/referent.rb', line 423

def type_of_thing
  genre = self.["genre"]
  genre = nil if genre =~ /^unknown$/i
  genre ||= "resource"

  genre = "book section" if genre =~ /^bookitem$/i

  return genre
end