Module: ActiveFedora::SemanticNode::ClassMethods

Defined in:
lib/active_fedora/semantic_node.rb

Instance Method Summary collapse

Instance Method Details

#create_bidirectional_named_relationship_methods(name, outbound_name) ⇒ Object

** EXPERIMENTAL **

Similar to +create_named_relationship_methods+ except we are merely creating an alias for outbound portion of bidirectional

====Example
  has_bidirectional_relationship "members", :has_collection_member, :is_member_of_collection

  Method members_outbound_append and members_outbound_remove added
  This method will create members_append which does same thing as members_outbound_append
  and will create members_remove which does same thing as members_outbound_remove


379
380
381
382
383
384
# File 'lib/active_fedora/semantic_node.rb', line 379

def create_bidirectional_named_relationship_methods(name,outbound_name)
  append_method_name = "#{name.to_s.downcase}_append"
  remove_method_name = "#{name.to_s.downcase}_remove"
  self.send(:define_method,:"#{append_method_name}") {|object| add_named_relationship(outbound_name,object)}
  self.send(:define_method,:"#{remove_method_name}") {|object| remove_named_relationship(outbound_name,object)}
end

#create_bidirectional_relationship_finders(name, outbound_predicate, inbound_predicate, opts = {}) ⇒ Object

Generates relationship finders for predicates that point in both directions and registers predicate relationships for each direction.

Parameters:

  • name (String)

    Name of the relationship method(s) to create

  • outbound_predicate (Symbol)

    Predicate used in outbound relationships

  • inbound_predicate (Symbol)

    Predicate used in inbound relationships

  • opts (Hash) (defaults to: {})

    (optional)



428
429
430
431
432
433
434
435
436
437
438
439
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
471
472
473
474
# File 'lib/active_fedora/semantic_node.rb', line 428

def create_bidirectional_relationship_finders(name, outbound_predicate, inbound_predicate, opts={})
  inbound_method_name = name.to_s+"_inbound"
  outbound_method_name = name.to_s+"_outbound"
  has_relationship(outbound_method_name, outbound_predicate, opts)
  has_relationship(inbound_method_name, inbound_predicate, opts.merge!(:inbound=>true))

  #create methods that mirror the outbound append and remove with our bidirectional name, assume just add and remove locally        
  create_bidirectional_relationship_name_methods(name,outbound_method_name)

  class_eval <<-END
  def #{name}(opts={})
    opts = {:rows=>25}.merge(opts)
    if opts[:response_format] == :solr || opts[:response_format] == :load_from_solr
      outbound_id_array = []
      predicate = outbound_relationship_predicates["#{name}_outbound"]
      if !outbound_relationships[predicate].nil? 
        outbound_relationships[predicate].each do |rel|
          outbound_id_array << rel.gsub("info:fedora/", "")
        end
      end
      #outbound_id_array = #{outbound_method_name}(:response_format=>:id_array)
      query = self.class.bidirectional_relationship_query(self.pid,"#{name}",outbound_id_array)
      solr_result = SolrService.instance.conn.query(query, :rows=>opts[:rows])
      
      if opts[:response_format] == :solr
        return solr_result
      elsif opts[:response_format] == :load_from_solr || self.load_from_solr
        return ActiveFedora::SolrService.reify_solr_results(solr_result,{:load_from_solr=>true})
      else
        return ActiveFedora::SolrService.reify_solr_results(solr_result)
      end
    else
      ary = #{inbound_method_name}(opts) + #{outbound_method_name}(opts)
      return ary.uniq
    end
  end
  def #{name}_ids
    #{name}(:response_format => :id_array)
  end
  def #{name}_from_solr
    #{name}(:response_format => :load_from_solr)
  end
  def #{name}_query
    relationship_query("#{name}")
  end
  END
end

#create_inbound_relationship_finders(name, predicate, opts = {}) ⇒ Object



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/active_fedora/semantic_node.rb', line 387

def create_inbound_relationship_finders(name, predicate, opts = {})
  class_eval <<-END
  def #{name}(opts={})
    load_inbound_relationship('#{name}', '#{predicate}', opts)
  end
  def #{name}_ids
    #{name}(:response_format => :id_array)
  end
  def #{name}_from_solr
    #{name}(:response_format => :load_from_solr)
  end
  def #{name}_query
    relationship_query("#{name}")
  end
  END
end

#create_named_relationship_methods(name) ⇒ Object

** EXPERIMENTAL **

Used in has_relationship call to create dynamic helper methods to append and remove objects to and from a named relationship

Example

For the following relationship

has_relationship "audio_records", :has_part, :type=>AudioRecord

Methods audio_records_append and audio_records_remove are created. Boths methods take an object that is kind_of? ActiveFedora::Base as a parameter



363
364
365
366
367
368
# File 'lib/active_fedora/semantic_node.rb', line 363

def create_named_relationship_methods(name)
  append_method_name = "#{name.to_s.downcase}_append"
  remove_method_name = "#{name.to_s.downcase}_remove"
  self.send(:define_method,:"#{append_method_name}") {|object| add_named_relationship(name,object)}
  self.send(:define_method,:"#{remove_method_name}") {|object| remove_named_relationship(name,object)}
end

#create_outbound_relationship_finders(name, predicate, opts = {}) ⇒ Object



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/active_fedora/semantic_node.rb', line 404

def create_outbound_relationship_finders(name, predicate, opts = {})
  class_eval <<-END
  def #{name}(opts={})
    load_outbound_relationship(#{name.inspect}, #{predicate.inspect}, opts)
  end
  def #{name}_ids
    #{name}(:response_format => :id_array)
  end
  def #{name}_from_solr
    #{name}(:response_format => :load_from_solr)
  end
  def #{name}_query
    relationship_query("#{name}")
  end
  END
end

#default_predicate_namespaceObject



545
546
547
# File 'lib/active_fedora/semantic_node.rb', line 545

def default_predicate_namespace
  predicate_config[:default_namespace]
end

#find_predicate(predicate) ⇒ Object



549
550
551
552
553
554
555
556
# File 'lib/active_fedora/semantic_node.rb', line 549

def find_predicate(predicate)
  predicate_mappings.each do |namespace,predicates|
    if predicates.fetch(predicate,nil)
      return predicates[predicate], namespace
    end
  end
  raise ActiveFedora::UnregisteredPredicateError, "Unregistered predicate: #{predicate.inspect}"
end

#has_bidirectional_relationship(name, outbound_predicate, inbound_predicate, opts = {}) ⇒ Object

Generates relationship finders for predicates that point in both directions

Example:

has_bidirectional_relationship("parts", :has_part, :is_part_of)

will create three instance methods: parts_outbound, and parts_inbound and parts the inbound and outbound methods are the same that would result from calling create_inbound_relationship_finders and create_outbound_relationship_finders The third method combines the results of both and handles generating appropriate solr queries where necessary.

Parameters:

  • name (String)

    of the relationship method(s) to create

  • outbound_predicate (Symbol)

    Predicate used in outbound relationships

  • inbound_predicate (Symbol)

    Predicate used in inbound relationships

  • opts (Hash) (defaults to: {})


298
299
300
# File 'lib/active_fedora/semantic_node.rb', line 298

def has_bidirectional_relationship(name, outbound_predicate, inbound_predicate, opts={})
  create_bidirectional_relationship_finders(name, outbound_predicate, inbound_predicate, opts)
end

#has_relationship(name, predicate, opts = {}) ⇒ Object

Allows for a relationship to be treated like any other attribute of a model class. You define relationships in your model class using this method. You then have access to several helper methods to list, append, and remove objects from the list of relationships.

Examples to define two relationships

class AudioRecord < ActiveFedora::Base

 has_relationship "oral_history", :has_part, :inbound=>true, :type=>OralHistory
 # returns all similar audio
 has_relationship "similar_audio", :has_part, :type=>AudioRecord
 #returns only similar audio with format wav
 has_relationship "similar_audio_wav", :has_part, :solr_fq=>"format_t:wav"

The first two parameters are required:

name: relationship name
predicate: predicate for the relationship
opts:
  possible parameters  
    :inbound => if true loads an external relationship via Solr (defaults to false)
    :type => The type of model to use when instantiated an object from the pid in this relationship (defaults to ActiveFedora::Base)
    :solr_fq => Define a solr query here if you want to filter out some objects in your relationship (must be a properly formatted solr query)

If inbound is true it expects the relationship to be defined by another object’s RELS-EXT and to load that relationship from Solr. Otherwise, if inbound is true the relationship is stored in this object’s RELS-EXT datastream

Word of caution - The same predicate may not be used twice for two inbound or two outbound relationships. However, it may be used twice if one is inbound and one is outbound as shown in the example above. A full list of possible predicates are defined by predicate_mappings

For the oral_history relationship in the example above the following helper methods are created:

oral_history: returns array of OralHistory objects that have this AudioRecord with predicate :has_part 
oral_history_ids: Return array of pids for OralHistory objects that have this AudioRecord with predicate :has_part
oral_history_query: Return solr query that can be used to retrieve related objects as solr documents

For the outbound relationship “similar_audio” there are two additional methods to append and remove objects from that relationship since it is managed internally:

similar_audio: Return array of AudioRecord objects that have been added to similar_audio relationship
similar_audio_ids:  Return array of AudioRecord object pids that have been added to similar_audio relationship
similar_audio_query: Return solr query that can be used to retrieve related objects as solr documents
similar_audio_append: Add an AudioRecord object to the similar_audio relationship
similar_audio_remove: Remove an AudioRecord from the similar_audio relationship


267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/active_fedora/semantic_node.rb', line 267

def has_relationship(name, predicate, opts = {})
  opts = {:singular => nil, :inbound => false}.merge(opts)
  if opts[:inbound] == true
    #raise "Duplicate use of predicate for inbound relationship name not allowed" if predicate_exists_with_different_relationship_name?(:inbound,name,predicate)
    register_relationship_desc(:inbound, name, predicate, opts)
    register_predicate(:inbound, predicate)
    create_inbound_relationship_finders(name, predicate, opts)
  else
    #raise "Duplicate use of predicate for named outbound relationship \"#{predicate.inspect}\" not allowed" if named_predicate_exists_with_different_name?(:self,name,predicate)
    register_relationship_desc(:self, name, predicate, opts)
    register_predicate(:self, predicate)
    create_relationship_name_methods(name)
    create_outbound_relationship_finders(name, predicate, opts)
  end
end

#named_predicate_exists_with_different_name?(subject, name, predicate) ⇒ Boolean

** EXPERIMENTAL **

Check to make sure a subject,name, and predicate triple does not already exist with the same subject but different name. This method is used to ensure conflicting has_relationship calls are not made because predicates cannot be reused across relationship names. Otherwise, the mapping of relationship name to predicate in RELS-EXT would be broken.

Returns:

  • (Boolean)


309
310
311
312
313
314
315
316
# File 'lib/active_fedora/semantic_node.rb', line 309

def named_predicate_exists_with_different_name?(subject,name,predicate)
  if named_relationships_desc.has_key?(subject)
    named_relationships_desc[subject].each_pair do |existing_name, args|
      return true if !args[:predicate].nil? && args[:predicate] == predicate && existing_name != name 
    end
  end
  return false
end

#named_relationships_descObject

** EXPERIMENTAL **

Return hash that stores named relationship metadata defined by has_relationship calls

Example

For the following relationship

has_relationship "audio_records", :has_part, :type=>AudioRecord

Results in the following returned by named_relationships_desc

{:self=>{"audio_records"=>{:type=>AudioRecord, :singular=>nil, :predicate=>:has_part, :inbound=>false}}}


327
328
329
330
# File 'lib/active_fedora/semantic_node.rb', line 327

def named_relationships_desc
  @class_named_relationships_desc ||= Hash[:self => {}]
  #class_named_relationships_desc
end

#predicate_configObject



537
538
539
# File 'lib/active_fedora/semantic_node.rb', line 537

def predicate_config
  @@predicate_config ||= YAML::load(File.open(ActiveFedora.predicate_config)) if File.exist?(ActiveFedora.predicate_config)
end

#predicate_lookup(predicate, namespace = "info:fedora/fedora-system:def/relations-external#") ⇒ Object

If predicate is a symbol, looks up the predicate in the predicate_mappings If predicate is not a Symbol, returns the predicate untouched

Raises:

  • UnregisteredPredicateError if the predicate is a symbol but is not found in the predicate_mappings



526
527
528
529
530
531
532
533
534
535
# File 'lib/active_fedora/semantic_node.rb', line 526

def predicate_lookup(predicate,namespace="info:fedora/fedora-system:def/relations-external#")
  if predicate.class == Symbol 
    if predicate_mappings[namespace].has_key?(predicate)
      return predicate_mappings[namespace][predicate]
    else
      raise ActiveFedora::UnregisteredPredicateError
    end
  end
  return predicate
end

#predicate_mappingsObject



541
542
543
# File 'lib/active_fedora/semantic_node.rb', line 541

def predicate_mappings
  predicate_config[:predicate_mapping]
end

#register_named_relationship(subject, name, predicate, opts) ⇒ Object

** EXPERIMENTAL **

Internal method that adds relationship name and predicate pair to either an outbound (:self) or inbound (:inbound) relationship types.



346
347
348
349
350
# File 'lib/active_fedora/semantic_node.rb', line 346

def register_named_relationship(subject, name, predicate, opts)
  register_named_subject(subject)
  opts.merge!({:predicate=>predicate})
  named_relationships_desc[subject][name] = opts
end

#register_named_subject(subject) ⇒ Object

** EXPERIMENTAL **

Internal method that ensures a relationship subject such as :self and :inbound exist within the named_relationships_desc hash tracking named relationships metadata.



336
337
338
339
340
# File 'lib/active_fedora/semantic_node.rb', line 336

def register_named_subject(subject)
  unless named_relationships_desc.has_key?(subject) 
    named_relationships_desc[subject] = {} 
  end
end

#register_predicate(subject, predicate) ⇒ Object



491
492
493
494
495
496
# File 'lib/active_fedora/semantic_node.rb', line 491

def register_predicate(subject, predicate)
  register_subject(subject)
  if !relationships[subject].has_key?(predicate) 
    relationships[subject][predicate] = []
  end
end

#register_subject(subject) ⇒ Object



485
486
487
488
489
# File 'lib/active_fedora/semantic_node.rb', line 485

def register_subject(subject)
  if !relationships.has_key?(subject) 
      relationships[subject] = {} 
  end
end

#relationshipsObject

relationships are tracked as a hash of structure

Examples:

ds.relationships # => {:self=>{:has_model=>["afmodel:SimpleThing"],:has_part=>["demo:20"]},:inbound=>{:is_part_of=>["demo:6"]} 


479
480
481
482
# File 'lib/active_fedora/semantic_node.rb', line 479

def relationships
  @class_relationships ||= Hash[:self => {}]
  #class_relationships
end

#relationships_to_rels_ext(pid, relationships = self.relationships) ⇒ Object

Creates a RELS-EXT datastream for insertion into a Fedora Object Note: This method is implemented on SemanticNode instead of RelsExtDatastream because SemanticNode contains the relationships array

Parameters:

  • pid (String)

    of the object that the RELS-EXT datastream belongs to

  • relationships (Hash) (defaults to: self.relationships)

    the relationships hash to transform into RELS-EXT RDF. @default the object’s relationships hash



504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/active_fedora/semantic_node.rb', line 504

def relationships_to_rels_ext(pid, relationships=self.relationships)
  starter_xml = <<-EOL
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about="info:fedora/#{pid}">
    </rdf:Description>
  </rdf:RDF>
  EOL
  xml = REXML::Document.new(starter_xml)

  # Iterate through the hash of predicates, adding an element to the RELS-EXT for each "object" in the predicate's corresponding array.
  self.outbound_relationships.each do |predicate, targets_array|
    targets_array.each do |target|
      #puts ". #{predicate} #{target}"
      xml.root.elements["rdf:Description"].add_element(predicate_lookup(predicate), {"xmlns" => "info:fedora/fedora-system:def/relations-external#", "rdf:resource"=>target})
    end
  end
  xml.to_s
end