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


599
600
601
602
603
604
# File 'lib/active_fedora/semantic_node.rb', line 599

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)



676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
# File 'lib/active_fedora/semantic_node.rb', line 676

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_named_relationship_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
      escaped_uri = self.internal_uri.gsub(/(:)/, '\\:')
      query = "#{inbound_predicate}_s:\#{escaped_uri}"
      
      outbound_id_array = #{outbound_method_name}(:response_format=>:id_array)
      query = query + " OR " + ActiveFedora::SolrService.construct_query_for_pids(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
  END
end

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



606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
# File 'lib/active_fedora/semantic_node.rb', line 606

def create_inbound_relationship_finders(name, predicate, opts = {})
  class_eval <<-END
  def #{name}(opts={})
    opts = {:rows=>25}.merge(opts)
    escaped_uri = self.internal_uri.gsub(/(:)/, '\\:')
    solr_result = SolrService.instance.conn.query("#{predicate}_s:\#{escaped_uri}", :rows=>opts[:rows])
    if opts[:response_format] == :solr
      return solr_result
    else
      if opts[:response_format] == :id_array
        id_array = []
        solr_result.hits.each do |hit|
          id_array << hit[SOLR_DOCUMENT_ID]
        end
        return id_array
      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
    end
  end
  def #{name}_ids
    #{name}(:response_format => :id_array)
  end
  def #{name}_from_solr
    #{name}(:response_format => :load_from_solr)
  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



583
584
585
586
587
588
# File 'lib/active_fedora/semantic_node.rb', line 583

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



637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/active_fedora/semantic_node.rb', line 637

def create_outbound_relationship_finders(name, predicate, opts = {})
  class_eval <<-END
  def #{name}(opts={})
    id_array = []
    if !outbound_relationships[#{predicate.inspect}].nil? 
      outbound_relationships[#{predicate.inspect}].each do |rel|
        id_array << rel.gsub("info:fedora/", "")
      end
    end
    if opts[:response_format] == :id_array
      return id_array
    else
      query = ActiveFedora::SolrService.construct_query_for_pids(id_array)
      solr_result = SolrService.instance.conn.query(query)
      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
    end
  end
  def #{name}_ids
    #{name}(:response_format => :id_array)
  end
  def #{name}_from_solr
    #{name}(:response_format => :load_from_solr)
  end
  END
end

#default_predicate_namespaceObject



786
787
788
# File 'lib/active_fedora/semantic_node.rb', line 786

def default_predicate_namespace
  predicate_config[:default_namespace]
end

#find_predicate(predicate) ⇒ Object



790
791
792
793
794
795
796
797
# File 'lib/active_fedora/semantic_node.rb', line 790

def find_predicate(predicate)
  predicate_mappings.each do |namespace,predicates|
    if predicates.fetch(predicate,nil)
      return predicates[predicate], namespace
    end
  end
  raise ActiveFedora::UnregisteredPredicateError
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: {})


519
520
521
# File 'lib/active_fedora/semantic_node.rb', line 519

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 named 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
 has_relationship "similar_audio", :has_part, :type=>AudioRecord

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)

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

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_append: Add an AudioRecord object to the similar_audio relationship
similar_audio_remove: Remove an AudioRecord from the similar_audio relationship


488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/active_fedora/semantic_node.rb', line 488

def has_relationship(name, predicate, opts = {})
  opts = {:singular => nil, :inbound => false}.merge(opts)
  if opts[:inbound] == true
    raise "Duplicate use of predicate for named inbound relationship not allowed" if named_predicate_exists_with_different_name?(:inbound,name,predicate)
    register_named_relationship(: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 not allowed" if named_predicate_exists_with_different_name?(:self,name,predicate)
    register_named_relationship(:self, name, predicate, opts)
    register_predicate(:self, predicate)
    create_named_relationship_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)


530
531
532
533
534
535
536
537
# File 'lib/active_fedora/semantic_node.rb', line 530

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}}}


548
549
550
# File 'lib/active_fedora/semantic_node.rb', line 548

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

#predicate_configObject



778
779
780
# File 'lib/active_fedora/semantic_node.rb', line 778

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



767
768
769
770
771
772
773
774
775
776
# File 'lib/active_fedora/semantic_node.rb', line 767

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



782
783
784
# File 'lib/active_fedora/semantic_node.rb', line 782

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.



566
567
568
569
570
# File 'lib/active_fedora/semantic_node.rb', line 566

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.



556
557
558
559
560
# File 'lib/active_fedora/semantic_node.rb', line 556

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

#register_predicate(subject, predicate) ⇒ Object



732
733
734
735
736
737
# File 'lib/active_fedora/semantic_node.rb', line 732

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

#register_subject(subject) ⇒ Object



726
727
728
729
730
# File 'lib/active_fedora/semantic_node.rb', line 726

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"]} 


721
722
723
# File 'lib/active_fedora/semantic_node.rb', line 721

def relationships
  @class_relationships ||= Hash[:self => {}]
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



745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
# File 'lib/active_fedora/semantic_node.rb', line 745

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