active-fedora 5.4.0 → 5.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,283 @@
1
+ module ActiveFedora
2
+ module RdfNode
3
+ extend ActiveSupport::Concern
4
+
5
+ ##
6
+ # Get the subject for this rdf object
7
+ def rdf_subject
8
+ @subject ||= begin
9
+ s = self.class.rdf_subject.call(self)
10
+ s &&= RDF::URI.new(s) if s.is_a? String
11
+ s
12
+ end
13
+ end
14
+
15
+ def reset_rdf_subject!
16
+ @subject = nil
17
+ end
18
+
19
+ # @param [Symbol, RDF::URI] predicate the predicate to insert into the graph
20
+ def get_values(subject, predicate)
21
+ options = config_for_term_or_uri(predicate)
22
+ return TermProxy.new(self, subject, options)
23
+ end
24
+
25
+
26
+ def target_class(predicate)
27
+ _, conf = self.class.config_for_predicate(predicate)
28
+ class_name = conf[:class_name]
29
+ return nil unless class_name
30
+ self.class.const_get(class_name.to_sym)
31
+ end
32
+
33
+ # if there are any existing statements with this predicate, replace them
34
+ # @param [Symbol, RDF::URI] predicate the predicate to insert into the graph
35
+
36
+ def set_value(subject, predicate, values)
37
+
38
+ #predicate = find_predicate(predicate) unless predicate.kind_of? RDF::URI
39
+ options = config_for_term_or_uri(predicate)
40
+ predicate = options[:predicate]
41
+
42
+ delete_predicate(subject, predicate)
43
+
44
+ Array(values).each do |arg|
45
+ arg = arg.to_s if arg.kind_of? RDF::Literal
46
+ next if arg.kind_of?(String) && arg.empty?
47
+
48
+ # If arg is a b-node, then copy it's statements onto the parent graph
49
+ arg = merge_subgraph(arg) if arg.respond_to? :graph
50
+ graph.insert([subject, predicate, arg])
51
+ end
52
+
53
+ return TermProxy.new(self, subject, options)
54
+ end
55
+
56
+
57
+ def delete_predicate(subject, predicate, values = nil)
58
+ predicate = find_predicate(predicate) unless predicate.kind_of? RDF::URI
59
+
60
+ if values.nil?
61
+ query = RDF::Query.new do
62
+ pattern [subject, predicate, :value]
63
+ end
64
+
65
+ query.execute(graph).each do |solution|
66
+ graph.delete [subject, predicate, solution.value]
67
+ end
68
+ else
69
+ Array(values).each do |v|
70
+ graph.delete [subject, predicate, v]
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ # append a value
77
+ # @param [Symbol, RDF::URI] predicate the predicate to insert into the graph
78
+ def append(subject, predicate, args)
79
+ options = config_for_term_or_uri(predicate)
80
+ graph.insert([subject, predicate, args])
81
+ TermProxy.new(self, subject, options)
82
+ end
83
+
84
+ def config_for_term_or_uri(term)
85
+ case term
86
+ when RDF::URI
87
+ self.class.config.each { |k, v| return v if v[:predicate] == term}
88
+ else
89
+ self.class.config[term.to_sym]
90
+ end
91
+ end
92
+
93
+ # @param [Symbol, RDF::URI] predicate the predicate to insert into the graph
94
+ def find_predicate(term)
95
+ conf = config_for_term_or_uri(term)
96
+ conf ? conf[:predicate] : nil
97
+ end
98
+
99
+ def query subject, predicate, &block
100
+ predicate = find_predicate(predicate) unless predicate.kind_of? RDF::URI
101
+
102
+ q = RDF::Query.new do
103
+ pattern [subject, predicate, :value]
104
+ end
105
+
106
+ q.execute(graph, &block)
107
+ end
108
+
109
+ def method_missing(name, *args)
110
+ if (md = /^([^=]+)=$/.match(name.to_s)) && pred = find_predicate(md[1])
111
+ set_value(rdf_subject, pred, *args)
112
+ elsif pred = find_predicate(name)
113
+ klass = target_class(pred)
114
+ if klass
115
+ # return an array of klass.new from each of the values
116
+ query(rdf_subject, pred).map do |solution|
117
+ klass.new(graph, solution.value)
118
+ end
119
+ else
120
+ get_values(rdf_subject, pred)
121
+ end
122
+ else
123
+ super
124
+ end
125
+ rescue ActiveFedora::UnregisteredPredicateError
126
+ super
127
+ end
128
+
129
+ private
130
+ # If arg is a b-node, then copy it's statements onto the parent graph
131
+ def merge_subgraph(rdf_object)
132
+ rdf_object.graph.statements.each do |s|
133
+ graph.insert(s)
134
+ end
135
+ # Return the arg to point at the new b-node
136
+ rdf_object.rdf_subject
137
+ end
138
+
139
+ class Builder
140
+ def initialize(parent)
141
+ @parent = parent
142
+ end
143
+
144
+ def build(&block)
145
+ yield self
146
+ end
147
+
148
+ def method_missing(name, *args, &block)
149
+ args = args.first if args.respond_to? :first
150
+ raise "mapping must specify RDF vocabulary as :in argument" unless args.has_key? :in
151
+ vocab = args[:in]
152
+ field = args.fetch(:to, name).to_sym
153
+ class_name = args[:class_name]
154
+ raise "Vocabulary '#{vocab.inspect}' does not define property '#{field.inspect}'" unless vocab.respond_to? field
155
+ indexing = false
156
+ if block_given?
157
+ # needed for solrizer integration
158
+ indexing = true
159
+ iobj = IndexObject.new
160
+ yield iobj
161
+ data_type = iobj.data_type
162
+ behaviors = iobj.behaviors
163
+ end
164
+ @parent.config[name] = {:predicate => vocab.send(field) }
165
+ # stuff data_type and behaviors in there for to_solr support
166
+ if indexing
167
+ @parent.config[name][:type] = data_type
168
+ @parent.config[name][:behaviors] = behaviors
169
+ end
170
+ @parent.config[name][:class_name] = class_name if class_name
171
+ end
172
+
173
+ # this enables a cleaner API for solr integration
174
+ class IndexObject
175
+ attr_accessor :data_type, :behaviors
176
+ def initialize
177
+ @behaviors = [:searchable]
178
+ @data_type = :string
179
+ end
180
+ def as(*args)
181
+ @behaviors = args
182
+ end
183
+ def type(sym)
184
+ @data_type = sym
185
+ end
186
+ def defaults
187
+ :noop
188
+ end
189
+ end
190
+ end
191
+
192
+ class TermProxy
193
+
194
+ attr_reader :graph, :subject, :predicate, :options
195
+ delegate :class, :to_s, :==, :kind_of?, :each, :map, :empty?, :as_json, :is_a?, :to => :values
196
+
197
+ def initialize(graph, subject, options)
198
+ @graph = graph
199
+
200
+ @subject = subject
201
+ @predicate = options[:predicate]
202
+ @options = options
203
+ end
204
+
205
+ def <<(*values)
206
+ values.each { |value| graph.append(subject, predicate, value) }
207
+ values
208
+ end
209
+
210
+ def delete(*values)
211
+ values.each do |value|
212
+ graph.delete_predicate(subject, predicate, value)
213
+ end
214
+
215
+ values
216
+ end
217
+
218
+ def values
219
+ values = []
220
+
221
+ graph.query(subject, predicate).each do |solution|
222
+ v = solution.value
223
+ v = v.to_s if v.is_a? RDF::Literal
224
+ if options[:type] == :date
225
+ v = Date.parse(v)
226
+ end
227
+ values << v
228
+ end
229
+
230
+ values
231
+ end
232
+
233
+ def method_missing(method, *args, &block)
234
+ if values.respond_to? method
235
+ values.send(method, *args, &block)
236
+ else
237
+ super
238
+ end
239
+ end
240
+ end
241
+ module ClassMethods
242
+ def config
243
+ @config ||= {}
244
+ end
245
+
246
+ def map_predicates(&block)
247
+ builder = Builder.new(self)
248
+ builder.build &block
249
+ end
250
+
251
+ def rdf_type(uri_or_string=nil)
252
+ if uri_or_string
253
+ uri = RDF::URI.new(uri_or_string) unless uri_or_string.kind_of? RDF::URI
254
+ self.config[:type] = {predicate: RDF.type}
255
+ @rdf_type = uri
256
+ end
257
+ @rdf_type
258
+ end
259
+
260
+ def config_for_predicate(predicate)
261
+ config.each do |term, value|
262
+ return term, value if value[:predicate] == predicate
263
+ end
264
+ return nil
265
+ end
266
+
267
+ ##
268
+ # Register a ruby block that evaluates to the subject of the graph
269
+ # By default, the block returns the current object's pid
270
+ # @yield [ds] 'ds' is the datastream instance
271
+ def rdf_subject &block
272
+ if block_given?
273
+ return @subject_block = block
274
+ end
275
+
276
+ # Create a B-node if they don't supply the rdf_subject
277
+ @subject_block ||= lambda { |ds| RDF::Node.new }
278
+ end
279
+
280
+ end
281
+ end
282
+ end
283
+
@@ -0,0 +1,32 @@
1
+ module ActiveFedora
2
+ module RdfObject
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include RdfNode
7
+ end
8
+
9
+ def graph
10
+ @graph ||= RDF::Graph.new
11
+ insert_type_assertion
12
+ @graph
13
+ end
14
+
15
+ def initialize(graph=RDF::Graph.new, subject=nil)
16
+ @graph = graph
17
+ @subject = subject
18
+ end
19
+
20
+ def get_values(subject, predicate)
21
+ predicate = find_predicate(predicate) unless predicate.kind_of? RDF::URI
22
+ return TermProxy.new(@graph, @subject, predicate)
23
+ end
24
+
25
+ private
26
+
27
+ def insert_type_assertion
28
+ rdf_type = self.class.rdf_type
29
+ @graph.insert([@subject, RDF.type, rdf_type]) if rdf_type
30
+ end
31
+ end
32
+ end
@@ -3,6 +3,7 @@ module ActiveFedora
3
3
  class SimpleDatastream < OmDatastream
4
4
 
5
5
  class_attribute :class_fields
6
+ attr_accessor :fields
6
7
  self.class_fields = []
7
8
 
8
9
 
@@ -17,6 +18,13 @@ module ActiveFedora
17
18
  end
18
19
 
19
20
 
21
+ #Constructor. this class will call self.field for each DCTERM. In short, all DCTERMS fields will already exist
22
+ #when this method returns. Each term is marked as a multivalue string.
23
+ def initialize(digital_object=nil, dsid=nil, options={})
24
+ super
25
+ self.fields={}
26
+ end
27
+
20
28
  # This method generates the various accessor and mutator methods on self for the datastream metadata attributes.
21
29
  # each field will have the 2 magic methods:
22
30
  # name=(arg)
@@ -1,4 +1,3 @@
1
- require "solrizer"
2
1
  require 'rsolr'
3
2
 
4
3
  module ActiveFedora
@@ -10,7 +9,6 @@ module ActiveFedora
10
9
  attr_reader :conn
11
10
 
12
11
  def self.register(host=nil, args={})
13
- load_mappings
14
12
  Thread.current[:solr_service]=self.new(host, args)
15
13
  end
16
14
 
@@ -1,3 +1,3 @@
1
1
  module ActiveFedora
2
- VERSION = "5.4.0"
2
+ VERSION = "5.5.0.rc1"
3
3
  end
@@ -1,4 +1,4 @@
1
- <info:fedora/test:1> <http://purl.org/dc/terms/created> "fake-date" .
1
+ <info:fedora/test:1> <http://purl.org/dc/terms/created> "2009-10-10" .
2
2
  <info:fedora/test:1> <http://purl.org/dc/terms/title> "fake-title" .
3
3
  <info:fedora/test:1> <http://purl.org/dc/terms/publisher> "publisher1" .
4
4
  <info:fedora/test:1> <http://xmlns.com/foaf/0.1/based_near> "coverage1" .
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Nested Rdf Objects" do
4
+ describe "without type" do
5
+ before(:each) do
6
+ class SpecDatastream < ActiveFedora::NtriplesRDFDatastream
7
+ map_predicates do |map|
8
+ map.parts(:in=> RDF::DC, :to=>'hasPart', :class_name=>'Component')
9
+ end
10
+
11
+ class Component
12
+ include ActiveFedora::RdfObject
13
+ map_predicates do |map|
14
+ map.label(:in=> RDF::DC, :to=>'title')
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ after(:each) do
22
+ Object.send(:remove_const, :SpecDatastream)
23
+ end
24
+
25
+ let (:ds) do
26
+ mock_obj = stub(:mock_obj, :pid=>'test:124', :new? => true)
27
+ ds = SpecDatastream.new(mock_obj)
28
+ end
29
+
30
+
31
+ it "should be able to nest a complex object" do
32
+ comp = SpecDatastream::Component.new
33
+ comp.label = ["Alternator"]
34
+ ds.parts = comp
35
+ ds.parts.first.label.should == ["Alternator"]
36
+ end
37
+ it "should be able to nest many complex objects" do
38
+ comp1 = SpecDatastream::Component.new
39
+ comp1.label = ["Alternator"]
40
+ comp2 = SpecDatastream::Component.new
41
+ comp2.label = ["Crankshaft"]
42
+ ds.parts = [comp1, comp2]
43
+ ds.parts.first.label.should == ["Alternator"]
44
+ ds.parts.last.label.should == ["Crankshaft"]
45
+ end
46
+
47
+ it "should be able to clear complex objects" do
48
+ comp1 = SpecDatastream::Component.new
49
+ comp1.label = ["Alternator"]
50
+ comp2 = SpecDatastream::Component.new
51
+ comp2.label = ["Crankshaft"]
52
+ ds.parts = [comp1, comp2]
53
+ ds.parts = []
54
+ ds.parts.should == []
55
+ end
56
+
57
+ it "should load complex objects" do
58
+ ds.content = <<END
59
+ _:g70350851837440 <http://purl.org/dc/terms/title> "Alternator" .
60
+ <info:fedora/test:124> <http://purl.org/dc/terms/hasPart> _:g70350851837440 .
61
+ <info:fedora/test:124> <http://purl.org/dc/terms/hasPart> _:g70350851833380 .
62
+ _:g70350851833380 <http://purl.org/dc/terms/title> "Crankshaft" .
63
+ END
64
+ ds.parts.first.label.should == ["Alternator"]
65
+ end
66
+ end
67
+
68
+ describe "with type" do
69
+ before(:each) do
70
+ class SpecDatastream < ActiveFedora::NtriplesRDFDatastream
71
+ map_predicates do |map|
72
+ map.mediator(:in=> RDF::DC, :class_name=>'MediatorUser')
73
+ end
74
+
75
+ class MediatorUser
76
+ include ActiveFedora::RdfObject
77
+ rdf_type "http://purl.org/dc/terms/AgentClass"
78
+ map_predicates do |map|
79
+ map.title(:in=> RDF::DC)
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ after(:each) do
87
+ Object.send(:remove_const, :SpecDatastream)
88
+ end
89
+
90
+ let (:ds) do
91
+ mock_obj = stub(:mock_obj, :pid=>'test:124', :new? => true)
92
+ ds = SpecDatastream.new(mock_obj)
93
+ end
94
+
95
+
96
+ it "should store the type of complex objects when type is specified" do
97
+ comp = SpecDatastream::MediatorUser.new
98
+ comp.title = ["Doctor"]
99
+ ds.mediator = comp
100
+ ds.mediator.first.type.first.should be_instance_of RDF::URI
101
+ ds.mediator.first.type.first.to_s.should == "http://purl.org/dc/terms/AgentClass"
102
+ ds.mediator.first.title.first.should == 'Doctor'
103
+ end
104
+
105
+ it "should add the type of complex object when it is not provided" do
106
+ ds.content = <<END
107
+ _:g70350851837440 <http://purl.org/dc/terms/title> "Mediation Person" .
108
+ <info:fedora/test:124> <http://purl.org/dc/terms/mediator> _:g70350851837440 .
109
+ END
110
+ ds.mediator.first.type.first.to_s.should == "http://purl.org/dc/terms/AgentClass"
111
+ end
112
+
113
+ it "should add load the type of complex objects when provided (superceeding what is specified by the class)" do
114
+ ds.content = <<END
115
+ _:g70350851837440 <http://purl.org/dc/terms/title> "Mediation Orgainzation" .
116
+ _:g70350851837440 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.ebu.ch/metadata/ontologies/ebucore#Organisation> .
117
+ <info:fedora/test:124> <http://purl.org/dc/terms/mediator> _:g70350851837440 .
118
+ END
119
+ ds.mediator.first.type.first.to_s.should == "http://www.ebu.ch/metadata/ontologies/ebucore#Organisation"
120
+ end
121
+ end
122
+ end