Class: RDF::SAK::Transform::Application

Inherits:
Partial
  • Object
show all
Defined in:
lib/rdf/sak/transform.rb

Overview

Note:

“Application” as in to “apply” a function, not an “app”.

A record of a transformation function application.

Instance Attribute Summary collapse

Attributes inherited from Partial

#subject

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Partial

#==

Constructor Details

#initialize(subject, transform, input, output, params = {}, start: nil, stop: nil) ⇒ Application

Create a new function application from whole cloth.

Parameters:

  • subject (RDF::Resource)
  • transform (RDF::Resource)

    the identifier for the transform

  • input (RDF::Resource)

    the identifier for the input

  • output (RDF::Resource)

    the identifier for the output

  • params (Hash, RDF::SAK::Transform::Partial) (defaults to: {})

    the parameters or partial application that is completed



901
902
903
904
905
906
907
908
909
910
911
# File 'lib/rdf/sak/transform.rb', line 901

def initialize subject, transform, input, output, params = {},
    start: nil, stop: nil
  # params may be a partial
  super subject, transform, params

  @input     = input
  @output    = output
  @completes = params if params.is_a? RDF::SAK::Transform::Partial
  @start     = start
  @stop      = stop
end

Instance Attribute Details

#completesObject (readonly)

Returns the value of attribute completes.



891
892
893
# File 'lib/rdf/sak/transform.rb', line 891

def completes
  @completes
end

#inputObject (readonly)

Returns the value of attribute input.



891
892
893
# File 'lib/rdf/sak/transform.rb', line 891

def input
  @input
end

#outputObject (readonly)

Returns the value of attribute output.



891
892
893
# File 'lib/rdf/sak/transform.rb', line 891

def output
  @output
end

Class Method Details

.resolve(harness, subject: nil, transform: nil, params: {}, partial: nil, input: nil, output: nil) ⇒ RDF::SAK::Transform::Application

Resolve a particular function Application from the repository. Either resolve by subject, or resolve by a transform + parameter + input set. Applications that complete Partials will be automatically resolved.

Parameters:

  • harness (RDF::SAK::Transform::Harness)

    the harness

  • subject (RDF::Resource) (defaults to: nil)

    the subject

  • transform (RDF::Resource, RDF::SAK::Transform) (defaults to: nil)

    the transform

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

    an instance of parameters

  • input (RDF::Resource) (defaults to: nil)

    the Application’s input

  • output (RDF::Resource) (defaults to: nil)

    the Application’s output

Returns:



766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
# File 'lib/rdf/sak/transform.rb', line 766

def self.resolve harness, subject: nil, transform: nil, params: {},
    partial: nil, input: nil, output: nil
  # either a subject or transform + input + output? + params?

  repo     = harness.repo
  partials = harness.partials

  if subject
    # noop
    return subject if subject.is_a? self

    # okay partial
    partial = RDF::SAK::Util.objects_for(
      subject, RDF::SAK::TFO.completes, only: :resource).sort.first

    if partial
      tmp = partials.resolve(subject: partial) or
        raise "Could not find partial #{partial}"
      partial   = tmp
      transform = partial.transform
    else
      transform = RDF::SAK::Util.objects_for(
        subject, RDF::SAK::TFO.transform, only: :resource).sort.first or
        raise "Could not find a transform for #{subject}"
      tmp = harness.resolve(transform) or
        raise "Could not find transform #{transform}"
      transform = tmp

      params = transform.validate 

      # get params
      params = {}
      transform.keys.each do |p|
        o = repo.query([subject, p, nil]).objects.uniq.sort
        params[p] = o unless o.empty?
      end
    end

    # get inputs and outputs
    input  = RDF::SAK::Util.objects_for(
      subject, RDF::SAK::TFO.input,  only: :resource).sort.first
    output = RDF::SAK::Util.objects_for(
      subject, RDF::SAK::TFO.output, only: :resource).sort.first

    raise 'Data must have both input and output' unless input and output
  elsif input and ((transform and params) or partial)

    # XXX dispatch on partial only? smart? dumb?
    if partial
      transform = partial.transform
      params    = partial.params
    else
      # do transform
      t = harness.resolve(transform) or
        raise "Could not resolve transform #{transform}"
      transform = t

      # coerce/validate params
      params = transform.validate params, defaults: false

      # do partial
      partial = partials.resolve transform: transform, params: params
    end

    # collect function application receipts
    candidates = RDF::Query.new do
      # note that there is no cost-based optimization so we write
      # these in the order of least to most cardinality
      pattern [:t, RDF::SAK::TFO.output, output] if output
      pattern [:t, RDF::SAK::TFO.input,  input]
    end.execute(repo).map { |sol| sol[:t] }.compact.uniq.select do |s|
      # this should say, try matching the partial if there is one
      # to match, otherwise attempt to directly match the transform
      if partial and repo.has_statement?(
        RDF::Statement(s, RDF::SAK::TFO.completes, partial.subject))
        true
      elsif repo.has_statement?(
        RDF::Statement(s, RDF::SAK::TFO.transform, transform.subject))
        testp = transform.keys.map do |p|
          o = repo.query([s, p, nil]).objects.uniq.sort
          o.empty? ? nil : [p, o]
        end.compact.to_h

        testp = transform.validate testp, defaults: false, silent: true
        testp == params
      end
    end.compact.uniq.sort

    return if candidates.empty?

    if candidates.size == 1
      subject = candidates.first
    else
      # now we have the unlikely case that there are two identical
      # records so we just sort em first by end date, then by
      # start date, then lexically
      subject = candidates.map do |s|
        st, et = %i[startedAtTime endedAtTime].map do |p|
          repo.query([s, RDF::Vocab::PROV[p], nil]).map do |stmt|
            dt = stmt.object.object
            dt if dt.is_a? DateTime
          end.compact.sort.last
        end
        [s, st, et]
      end.sort do |a, b|
        # first check latest end-time, then check latest start-time
        c = a[2] && b[2] ? b[2] <=> a[2] : 0
        # if those two yield nothing, then sort lexically i guess
        (c == 0 && a[1] && b[1]) ? b[1] <=> a[1] : a[0] <=> b[0]
      end.first.first
    end
  else
    raise ArgumentError,
      'must have either a subject or transform + params + input'
  end

  # don't forget the output
  output ||= repo.query(
    [subject, RDF::SAK::TFO.output, nil]
  ).objects.select(&:uri?).sort.first

  new subject, transform, input, output, partial || params

end

Instance Method Details

#===(other) ⇒ Object



968
969
970
971
972
973
974
975
976
977
# File 'lib/rdf/sak/transform.rb', line 968

def ===(other)
   return false unless other.is_a? Application
   return false unless @input == other.input and @output == other.output

   # now the comparand is either the partial or us
   cmp = @completes || self

   # and this should do it
   other.transform == cmp.transform and other.matches? cmp.params
end

#[](key) ⇒ Object



942
943
944
945
# File 'lib/rdf/sak/transform.rb', line 942

def [](key)
  # note complete is
  (@completes || @params)[key]
end

#completes?(partial) ⇒ Boolean

Returns:

  • (Boolean)


959
960
961
# File 'lib/rdf/sak/transform.rb', line 959

def completes? partial
  @completes and partial and @completes == partial
end

#keysObject



947
948
949
# File 'lib/rdf/sak/transform.rb', line 947

def keys
  (@completes || @params).keys
end

#matches?(params) ⇒ Boolean

Returns:

  • (Boolean)


963
964
965
966
# File 'lib/rdf/sak/transform.rb', line 963

def matches? params
  return @completes.matches? params if @completes
  super params
end

#paramsObject



951
952
953
# File 'lib/rdf/sak/transform.rb', line 951

def params
  @completes ? @completes.params : @params.dup
end

#to_triplesObject

Returns the function application as an array of triples.



914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
# File 'lib/rdf/sak/transform.rb', line 914

def to_triples
  out = [] # .extend RDF::Enumerable
  s = @subject
  out << [s, RDF.type, RDF::SAK::TFO.Application]

  if @start
    start = @start.is_a?(RDF::Literal) ? @start : RDF::Literal(@start) 
    out << [s, RDF::Vocab::PROV.startedAtTime, start]
  end

  if @stop
    stop = @stop.is_a?(RDF::Literal) ? @stop : RDF::Literal(@stop)
    out << [s, RDF::Vocab::PROV.endedAtTime, stop]
  end

  if @completes
    out << [s, RDF::SAK::TFO.completes, @completes.subject]
  else
    out << [s, RDF::SAK::TFO.transform, transform.subject]
    pdup = transform.validate params, defaults: false, silent: true
    pdup.each do |k, vals|
      vals.each { |v| out << [s, k, v] }
    end
  end

  out.map { |triples| RDF::Statement(*triples) }
end

#transformObject



955
956
957
# File 'lib/rdf/sak/transform.rb', line 955

def transform
  @completes ? @completes.transform : @transform
end