active-fedora 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-02-04
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,19 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ lib/active_fedora.rb
6
+ lib/active-fedora.rb
7
+ lib/active_fedora/base.rb
8
+ lib/active_fedora/content_model.rb
9
+ lib/active_fedora/datastream.rb
10
+ lib/active_fedora/fedora_object.rb
11
+ lib/active_fedora/metadata_datastream.rb
12
+ lib/active_fedora/model.rb
13
+ lib/active_fedora/property.rb
14
+ lib/active_fedora/qualified_dublin_core_datastream.rb
15
+ lib/active_fedora/relationship.rb
16
+ lib/active_fedora/rels_ext_datastream.rb
17
+ lib/active_fedora/semantic_node.rb
18
+ lib/active_fedora/solr_service.rb
19
+ solr/config/schema.xml
@@ -0,0 +1,7 @@
1
+
2
+ For more information on afed-regem, see http://afed-regem.rubyforge.org
3
+
4
+ NOTE: Change this information in PostInstall.txt
5
+ You can also delete it if you don't want it.
6
+
7
+
@@ -0,0 +1,48 @@
1
+ = afed-regem
2
+
3
+ * FIX (url)
4
+
5
+ == DESCRIPTION:
6
+
7
+ FIX (describe your package)
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * FIX (list of features or problems)
12
+
13
+ == SYNOPSIS:
14
+
15
+ FIX (code sample of usage)
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * FIX (list of requirements)
20
+
21
+ == INSTALL:
22
+
23
+ * FIX (sudo gem install, anything else)
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2009 FIXME full name
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ require 'active_fedora'
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ gem 'ruby-fedora'
3
+ gem 'solr-ruby'
4
+ module ActiveFedora #:nodoc:
5
+ VERSION='1.0.0'
6
+ end
7
+
8
+
9
+
10
+ Dir[File.join(File.dirname(__FILE__), 'lib/active_fedora')+'/**/*.rb'].each{|x| "requiring #{x}";require x}
@@ -0,0 +1,362 @@
1
+ require 'util/class_level_inheritable_attributes'
2
+ require 'active_fedora/model'
3
+ require 'active_fedora/semantic_node'
4
+ module ActiveFedora
5
+
6
+ # This class ties together many of the lower-level modules, and
7
+ # implements something akin to an ActiveRecord-alike interface to
8
+ # fedora. If you want to represent a fedora object in the ruby
9
+ # space, this is the class you want to extend.
10
+ #
11
+ # =The Basics
12
+ # class Oralhistory < ActiveFedora::Base
13
+ # has_metadata :name => "properties", :type => ActiveFedora::MetadataDatastream do |m|
14
+ # m.field "narrator", :string
15
+ # m.field "narrator", :text
16
+ # end
17
+ # end
18
+ #
19
+ # The above example creates a FedoraObject with a metadata datastream named "properties", which is composed of a
20
+ # narrator and bio field.
21
+ #
22
+ # Datastreams defined with +has_metadata+ are accessed via the +datastreams+ member hash.
23
+ #
24
+ # =Implementation
25
+ # This class is really a facade for a basic Fedora::FedoraObject, which is stored internally.
26
+ class Base
27
+ include MediaShelfClassLevelInheritableAttributes
28
+ ms_inheritable_attributes :ds_specs
29
+ include Model
30
+ include SemanticNode
31
+
32
+ # Has this object been saved?
33
+ def new_object?
34
+ @new_object
35
+ end
36
+
37
+ # Constructor. If +attrs+ does not comtain +:pid+, we assume we're making a new one,
38
+ # and call off to the Fedora Rest API for the next available Fedora pid, and mark as new object.
39
+ #
40
+ # If there is a pid, we're re-hydrating an existing object, and new object is false. Once the @inner_object is stored,
41
+ # we configure any defined datastreams.
42
+ def initialize(attrs = {})
43
+ unless attrs[:pid]
44
+ attrs = attrs.merge!({:pid=>Fedora::Repository.instance.nextid})
45
+ @new_object=true
46
+ else
47
+ @new_object=false;
48
+ end
49
+ @inner_object = Fedora::FedoraObject.new(attrs)
50
+ @datastreams = {}
51
+ configure_defined_datastreams
52
+ end
53
+
54
+ #This method is used to specify the details of a datastream.
55
+ #args must include :name. Note that this method doesn't actually
56
+ #execute the block, but stores it at the class level, to be executed
57
+ #by any future instantiations.
58
+ def self.has_metadata(args, &block)
59
+ @ds_specs ||= Hash.new
60
+ @ds_specs[args[:name]]= [args[:type], block]
61
+ end
62
+
63
+ #Saves a Base object, and any dirty datastreams, then updates
64
+ #the Solr index for this object.
65
+ def save
66
+ # If it's a new object, set the conformsTo relationship for Fedora CMA
67
+ if new_object?
68
+ add_relationship(:conforms_to, ActiveFedora::ContentModel.pid_from_ruby_class(self.class))
69
+ end
70
+ @new_object =false
71
+ Fedora::Repository.instance.save(@inner_object)
72
+ datastreams_in_memory.each do |k,ds|
73
+ if ds.dirty? || ds.new_object?
74
+ if ds.kind_of?(ActiveFedora::MetadataDatastream)
75
+ metadata_is_dirty = true
76
+ end
77
+ ds.save
78
+ end
79
+ self.update_index if metadata_is_dirty == true
80
+ end
81
+ end
82
+
83
+ #Deletes a Base object, also deletes the info indexed in Solr, and
84
+ #the underlying inner_object.
85
+ def delete
86
+ Fedora::Repository.instance.delete(@inner_object)
87
+ escaped_pid = self.pid.gsub(/(:)/, '\\:')
88
+ SolrService.instance.conn.delete(escaped_pid)
89
+ end
90
+
91
+ # Returns all known datastreams for the object. If the object has been
92
+ # saved to fedora, the persisted datastreams will be included.
93
+ # Datastreams that have been modified in memory are given preference over
94
+ # the copy in Fedora.
95
+ def datastreams
96
+ if @new_object
97
+ @datastreams = datastreams_in_memory
98
+ else
99
+ @datastreams = datastreams_in_fedora.merge(datastreams_in_memory)
100
+ end
101
+
102
+ end
103
+
104
+ def datastreams_in_fedora #:nodoc:
105
+ mds = {}
106
+ self.datastreams_xml['datastream'].each do |ds|
107
+ ds.merge!({:pid => self.pid, :dsID => ds["dsid"]})
108
+ if ds["dsid"] == "RELS-EXT"
109
+ mds.merge!({ds["dsid"] => ActiveFedora::RelsExtDatastream.new(ds)})
110
+ else
111
+ mds.merge!({ds["dsid"] => ActiveFedora::Datastream.new(ds)})
112
+ end
113
+ mds[ds["dsid"]].new_object = false
114
+ end
115
+ mds
116
+ end
117
+
118
+ def datastreams_in_memory #:ndoc:
119
+ @datastreams ||= Hash.new
120
+ end
121
+
122
+ #return the datastream xml representation direclty from Fedora
123
+ def datastreams_xml
124
+ datastreams_xml = XmlSimple.xml_in(Fedora::Repository.instance.fetch_custom(self.pid, :datastreams))
125
+ end
126
+
127
+ # Adds datastream to the object. Saves the datastream to fedora upon adding.
128
+ def add_datastream(datastream)
129
+ datastream.pid = self.pid
130
+ datastreams[datastream.dsid] = datastream
131
+ return true
132
+ end
133
+ def add(datastream) # :nodoc:
134
+ warn "Warning: ActiveFedora::Base.add has been deprected. Use add_datastream"
135
+ add_datastream(datastream)
136
+ end
137
+
138
+ #return all datastreams of type ActiveFedora::MetadataDatastream
139
+ def metadata_streams
140
+ results = []
141
+ datastreams.each_value do |ds|
142
+ if ds.kind_of?(ActiveFedora::MetadataDatastream)
143
+ results<<ds
144
+ end
145
+ end
146
+ return results
147
+ end
148
+
149
+ #return all datastreams not of type ActiveFedora::MetadataDatastream
150
+ #(that aren't Dublin Core or RELS-EXT streams either)
151
+ def file_streams
152
+ results = []
153
+ datastreams.each_value do |ds|
154
+ if !ds.kind_of?(ActiveFedora::MetadataDatastream)
155
+ dsid = ds.dsid
156
+ if dsid != "DC" && dsid != "RELS-EXT"
157
+ results<<ds
158
+ end
159
+ end
160
+ end
161
+ return results
162
+ end
163
+
164
+ # Return the Dublin Core (DC) Datastream. You can also get at this via
165
+ # the +datastreams["DC"]+.
166
+ def dc
167
+ #dc = REXML::Document.new(datastreams["DC"].content)
168
+ return datastreams["DC"]
169
+ end
170
+
171
+ # Returns the RELS-EXT Datastream
172
+ # Tries to grab from in-memory datastreams first
173
+ # Failing that, attempts to load from Fedora and addst to in-memory datastreams
174
+ # Failing that, creates a new RelsExtDatastream and adds it to the object
175
+ def rels_ext
176
+ if !datastreams.has_key?("RELS-EXT")
177
+ add_datastream(ActiveFedora::RelsExtDatastream.new)
178
+ end
179
+ return datastreams["RELS-EXT"]
180
+ end
181
+
182
+ # @returns Hash of relationships, as defined by SemanticNode
183
+ # Rely on rels_ext datastream to track relationships array
184
+ # Overrides accessor for relationships array used by SemanticNode.
185
+ def relationships
186
+ return rels_ext.relationships
187
+ end
188
+
189
+ # Add a Rels-Ext relationship to the Object.
190
+ # @param predicate
191
+ # @param object Either a string URI or an object that responds to .pid
192
+ def add_relationship(predicate, obj)
193
+ #predicate = ActiveFedora::RelsExtDatastream.predicate_lookup(predicate)
194
+ r = ActiveFedora::Relationship.new(:subject=>:self, :predicate=>predicate, :object=>obj)
195
+ rels_ext.add_relationship(r)
196
+ rels_ext.dirty = true
197
+ end
198
+
199
+
200
+ def inner_object # :nodoc
201
+ @inner_object
202
+ end
203
+
204
+ #return the pid of the Fedora Object
205
+ def pid
206
+ @inner_object.pid
207
+ end
208
+
209
+ #For Rails compatibility with url generators.
210
+ def to_param
211
+ self.pid
212
+ end
213
+ #return the internal fedora URI
214
+ def internal_uri
215
+ "info:fedora/#{pid}"
216
+ end
217
+
218
+ #return the state of the inner object
219
+ def state
220
+ @inner_object.state
221
+ end
222
+
223
+ #return the owner id
224
+ def owner_id
225
+ @inner_object.owner_id
226
+ end
227
+
228
+ #return the create_date of the inner object (unless it's a new object)
229
+ def create_date
230
+ @inner_object.create_date unless new_object?
231
+ end
232
+
233
+ #return the modification date of the inner object (unless it's a new object)
234
+ def modified_date
235
+ @inner_object.modified_date unless new_object?
236
+ end
237
+
238
+ #return the error list of the inner object (unless it's a new object)
239
+ def errors
240
+ @inner_object.errors
241
+ end
242
+
243
+ #return the label of the inner object (unless it's a new object)
244
+ def label
245
+ @inner_object.label
246
+ end
247
+
248
+
249
+ def self.deserialize(doc) #:nodoc:
250
+ pid = doc.elements['/foxml:digitalObject'].attributes['PID']
251
+ proto = self.new(:pid=>pid)
252
+ proto.datastreams.each do |name,ds|
253
+ doc.elements.each("//foxml:datastream[@ID='#{name}']") do |el|
254
+ proto.datastreams[name]=ds.class.from_xml(ds, el)
255
+ end
256
+ end
257
+ proto
258
+ end
259
+
260
+ #Return a hash of all available metadata fields for all
261
+ #ActiveFedora::MetadataDatastream datastreams, as well as
262
+ #system_create_date, system_modified_date, active_fedora_model_field,
263
+ #and the object id.
264
+ def fields
265
+ fields = {:id => {:values => [pid]}, :system_create_date => {:values => [self.create_date]}, :system_modified_date => {:values => [self.modified_date]}, :active_fedora_model_field => {:values => [self.class.inspect]}}
266
+ datastreams.values.each do |ds|
267
+ fields.merge!(ds.fields) if ds.kind_of?(ActiveFedora::MetadataDatastream)
268
+ end
269
+ return fields
270
+ end
271
+
272
+ #Returns the xml version of this object as a string.
273
+ def to_xml(xml=REXML::Document.new("<xml><fields/><content/></xml>"))
274
+ fields_xml = xml.root.elements['fields']
275
+ {:id => pid, :system_create_date => self.create_date, :system_modified_date => self.modified_date, :active_fedora_model_field => self.class.inspect}.each_pair do |attribute_name, value|
276
+ el = REXML::Element.new(attribute_name.to_s)
277
+ el.text = value
278
+ fields_xml << el
279
+ end
280
+ datastreams.each_value do |ds|
281
+ ds.to_xml(fields_xml) if ds.kind_of?(ActiveFedora::MetadataDatastream) || ds.kind_of?(ActiveFedora::RelsExtDatastream)
282
+ end
283
+ return xml.to_s
284
+ end
285
+
286
+ #Return a Solr::Document version of this object.
287
+ def to_solr(solr_doc = Solr::Document.new)
288
+ solr_doc << {:id => pid, :system_create_date => self.create_date, :system_modified_date => self.modified_date, :active_fedora_model_field => self.class.inspect}
289
+ datastreams.each_value do |ds|
290
+ solr_doc = ds.to_solr(solr_doc) if ds.kind_of?(ActiveFedora::MetadataDatastream) || ds.kind_of?(ActiveFedora::RelsExtDatastream)
291
+ end
292
+ return solr_doc
293
+ end
294
+
295
+ # Updates Solr index with self.
296
+ def update_index
297
+ SolrService.instance.conn.update(self.to_solr)
298
+ end
299
+
300
+ # An ActiveRecord-ism to udpate metadata values.
301
+ #
302
+ # Passing a hash into this method will attempt to set the values into the
303
+ # appropriate fields (for all datastreams, which means a.name and b.name
304
+ # fields are _both_ overwritten)
305
+ def update_attributes(params={})
306
+ params.each do |k,v|
307
+ datastreams.values.each do |d|
308
+ if d.fields[k.to_sym]
309
+ d.send("#{k}_values=", v)
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ # A convenience method for updating indexed attributes. The passed in hash
316
+ # must look like this :
317
+ # {{:name=>{"0"=>"a","1"=>"b"}}
318
+ #
319
+ # This will result in any datastream field of name :name having the value [a,b]
320
+ #
321
+ # An index of -1 will insert a new value. any existing value at the relevant index
322
+ # will be overwritten.
323
+ #
324
+ # As in update_attributes, this overwrites _all_ available fields.
325
+ #
326
+ def update_indexed_attributes(params={})
327
+ params.each do |key,value|
328
+ datastreams.each do |dsn, dstream|
329
+ if dstream.fields[key.to_sym]
330
+ aname="#{key}_values"
331
+ curval = dstream.send("#{aname}")
332
+ cpv=value.dup#copy this, we'll need the original for the next ds
333
+ cpv.delete_if do |y,z|
334
+ if curval[y.to_i] and y.to_i > -1
335
+ curval[y.to_i]=z
336
+ true
337
+ else
338
+ false
339
+ end
340
+ end
341
+ cpv.each { |y,z| curval<<z}#just append everything left
342
+ dstream.send("#{aname}=", curval) #write it back to the ds
343
+ end
344
+ end
345
+ end
346
+
347
+ end
348
+
349
+ private
350
+ def configure_defined_datastreams
351
+ if self.class.ds_specs
352
+ self.class.ds_specs.each do |name,ar|
353
+ ds = ar.first.new(:dsid=>name)
354
+ ar.last.call(ds)
355
+ self.add_datastream(ds)
356
+ end
357
+ end
358
+ end
359
+
360
+
361
+ end
362
+ end