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


593
594
595
596
597
598
# File 'lib/active_fedora/semantic_node.rb', line 593

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)



670
671
672
673
674
675
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
# File 'lib/active_fedora/semantic_node.rb', line 670

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



600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
# File 'lib/active_fedora/semantic_node.rb', line 600

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



577
578
579
580
581
582
# File 'lib/active_fedora/semantic_node.rb', line 577

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



631
632
633
634
635
636
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
# File 'lib/active_fedora/semantic_node.rb', line 631

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



780
781
782
# File 'lib/active_fedora/semantic_node.rb', line 780

def default_predicate_namespace
  predicate_config[:default_namespace]
end

#find_predicate(predicate) ⇒ Object



784
785
786
787
788
789
790
791
792
# File 'lib/active_fedora/semantic_node.rb', line 784

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


513
514
515
# File 'lib/active_fedora/semantic_node.rb', line 513

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


482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/active_fedora/semantic_node.rb', line 482

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)


524
525
526
527
528
529
530
531
# File 'lib/active_fedora/semantic_node.rb', line 524

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


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

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

#predicate_configObject



772
773
774
# File 'lib/active_fedora/semantic_node.rb', line 772

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



761
762
763
764
765
766
767
768
769
770
# File 'lib/active_fedora/semantic_node.rb', line 761

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



776
777
778
# File 'lib/active_fedora/semantic_node.rb', line 776

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.



560
561
562
563
564
# File 'lib/active_fedora/semantic_node.rb', line 560

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.



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

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

#register_predicate(subject, predicate) ⇒ Object



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

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

#register_subject(subject) ⇒ Object



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

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


715
716
717
# File 'lib/active_fedora/semantic_node.rb', line 715

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



739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
# File 'lib/active_fedora/semantic_node.rb', line 739

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