class ActiveFedora::Base
This class is really a facade for a basic Fedora::FedoraObject, which is stored internally.
=Implementation
Datastreams defined with has_metadata
are accessed via the datastreams
member hash.
narrator and bio field.
The above example creates a FedoraObject with a metadata datastream named “properties”, which is composed of a
end
end
m.field “narrator”, :text
m.field “narrator”, :string
has_metadata :name => “properties”, :type => ActiveFedora::MetadataDatastream do |m|
class Oralhistory < ActiveFedora::Base
=The Basics
space, this is the class you want to extend.
fedora. If you want to represent a fedora object in the ruby
implements something akin to an ActiveRecord-alike interface to
This class ties together many of the lower-level modules, and
def self.deserialize(doc) #:nodoc:
def self.deserialize(doc) #:nodoc: pid = doc.elements['/foxml:digitalObject'].attributes['PID'] proto = self.new(:pid=>pid, :new_object=>false) proto.datastreams.each do |name,ds| doc.elements.each("//foxml:datastream[@ID='#{name}']") do |el| # datastreams remain marked as new if the foxml doesn't have an entry for that datastream ds.new_object = false proto.datastreams[name]=ds.class.from_xml(ds, el) end end proto.inner_object.new_object = false return proto end
def self.has_metadata(args, &block)
execute the block, but stores it at the class level, to be executed
args must include :name. Note that this method doesn't actually
This method is used to specify the details of a datastream.
def self.has_metadata(args, &block) @ds_specs ||= Hash.new @ds_specs[args[:name]]= [args[:type], block] end
def self.pids_from_uris(uris)
def self.pids_from_uris(uris) if uris.class == String return uris.gsub("info:fedora/", "") elsif uris.class == Array arr = [] uris.each do |uri| arr << uri.gsub("info:fedora/", "") end return arr end end
def add(datastream) # :nodoc:
def add(datastream) # :nodoc: warn "Warning: ActiveFedora::Base.add has been deprected. Use add_datastream" add_datastream(datastream) end
def add_datastream(datastream, opts={})
:prefix option will set the prefix on auto-generated DSID
If datastream does not have a DSID, a unique DSID is generated
Adds datastream to the object. Saves the datastream to fedora upon adding.
def add_datastream(datastream, opts={}) datastream.pid = self.pid if datastream.dsid == nil || datastream.dsid.empty? prefix = opts.has_key?(:prefix) ? opts[:prefix] : "DS" datastream.dsid = generate_dsid(prefix) end datastreams[datastream.dsid] = datastream return datastream.dsid end
def add_file_datastream(file, opts={})
def add_file_datastream(file, opts={}) label = opts.has_key?(:label) ? opts[:label] : "" ds = ActiveFedora::Datastream.new(:dsLabel => label, :controlGroup => 'M', :blob => file) opts.has_key?(:dsid) ? ds.dsid=(opts[:dsid]) : nil add_datastream(ds) end
def add_relationship(predicate, obj)
-
object
() -- Either a string URI or an object that responds to .pid
-
predicate
() --
def add_relationship(predicate, obj) #predicate = ActiveFedora::RelsExtDatastream.predicate_lookup(predicate) r = ActiveFedora::Relationship.new(:subject=>:self, :predicate=>predicate, :object=>obj) rels_ext.add_relationship(r) rels_ext.dirty = true end
def collection_members_append(obj)
def collection_members_append(obj) add_relationship(:has_collection_member, obj) end
def collection_members_remove()
def collection_members_remove() # will rely on SemanticNode.remove_relationship once it is implemented end
def configure_defined_datastreams
def configure_defined_datastreams if self.class.ds_specs self.class.ds_specs.each do |name,ar| ds = ar.first.new(:dsid=>name) ar.last.call(ds) self.add_datastream(ds) end end end
def create
def create add_relationship(:has_model, ActiveFedora::ContentModel.pid_from_ruby_class(self.class)) @metadata_is_dirty = true update #@datastreams = datastreams_in_fedora end
def create_date
def create_date @inner_object.create_date unless new_object? end
def datastreams
Datastreams that have been modified in memory are given preference over
saved to fedora, the persisted datastreams will be included.
Returns all known datastreams for the object. If the object has been
def datastreams if @new_object @datastreams = datastreams_in_memory else @datastreams = (@datastreams == {}) ? datastreams_in_fedora : datastreams_in_memory #@datastreams = datastreams_in_fedora.merge(datastreams_in_memory) end end
def datastreams_in_fedora #:nodoc:
def datastreams_in_fedora #:nodoc: mds = {} self.datastreams_xml['datastream'].each do |ds| ds.merge!({:pid => self.pid, :dsID => ds["dsid"], :dsLabel => ds["label"]}) if ds["dsid"] == "RELS-EXT" mds.merge!({ds["dsid"] => ActiveFedora::RelsExtDatastream.new(ds)}) else mds.merge!({ds["dsid"] => ActiveFedora::Datastream.new(ds)}) end mds[ds["dsid"]].new_object = false end mds end
def datastreams_in_memory #:ndoc:
def datastreams_in_memory #:ndoc: @datastreams ||= Hash.new end
def datastreams_xml
def datastreams_xml datastreams_xml = XmlSimple.xml_in(Fedora::Repository.instance.fetch_custom(self.pid, :datastreams)) end
def dc
Return the Dublin Core (DC) Datastream. You can also get at this via
def dc #dc = REXML::Document.new(datastreams["DC"].content) return datastreams["DC"] end
def delete
Deletes a Base object, also deletes the info indexed in Solr, and
def delete Fedora::Repository.instance.delete(@inner_object) escaped_pid = self.pid.gsub(/(:)/, '\\:') SolrService.instance.conn.delete(escaped_pid) if ENABLE_SOLR_UPDATES end
def errors
def errors @inner_object.errors end
def fields
system_create_date, system_modified_date, active_fedora_model_field,
ActiveFedora::MetadataDatastream datastreams, as well as
Return a hash of all available metadata fields for all
def fields fields = {:id => {:values => [pid]}, :system_create_date => {:values => [self.create_date], :type=>:date}, :system_modified_date => {:values => [self.modified_date], :type=>:date}, :active_fedora_model => {:values => [self.class.inspect], :type=>:symbol}} datastreams.values.each do |ds| fields.merge!(ds.fields) if ds.kind_of?(ActiveFedora::MetadataDatastream) end return fields end
def file_objects
def file_objects collection_members end
def file_objects_append(obj)
def file_objects_append(obj) collection_members_append(obj) end
def file_streams
return all datastreams not of type ActiveFedora::MetadataDatastream
def file_streams results = [] datastreams.each_value do |ds| if !ds.kind_of?(ActiveFedora::MetadataDatastream) dsid = ds.dsid if dsid != "DC" && dsid != "RELS-EXT" results<<ds end end end return results end
def generate_dsid(prefix="DS")
return a valid dsid that is not currently in use. Uses a prefix (default "DS") and an auto-incrementing integer
def generate_dsid(prefix="DS") keys = datastreams.keys next_index = keys.select {|v| v =~ /(#{prefix}\d*$)/}.length + 1 new_dsid = prefix.to_s + next_index.to_s # while keys.include?(new_dsid) # next_index += 1 # new_dsid = prefix.to_s + rand(range).to_s # end end
def initialize(attrs = {})
If there is a pid, we're re-hydrating an existing object, and new object is false. Once the @inner_object is stored,
and call off to the Fedora Rest API for the next available Fedora pid, and mark as new object.
Constructor. If +attrs+ does not comtain +:pid+, we assume we're making a new one,
def initialize(attrs = {}) unless attrs[:pid] attrs = attrs.merge!({:pid=>Fedora::Repository.instance.nextid}) @new_object=true else @new_object = attrs[:new_object] == false ? false : true end @inner_object = Fedora::FedoraObject.new(attrs) @datastreams = {} configure_defined_datastreams end
def inner_object # :nodoc
def inner_object # :nodoc @inner_object end
def internal_uri
def internal_uri "info:fedora/#{pid}" end
def label
def label @inner_object.label end
def label=(new_label)
def label=(new_label) @inner_object.label = new_label end
def metadata_streams
def metadata_streams results = [] datastreams.each_value do |ds| if ds.kind_of?(ActiveFedora::MetadataDatastream) results<<ds end end return results end
def modified_date
def modified_date @inner_object.modified_date unless new_object? end
def new_object=(bool)
def new_object=(bool) @new_object = bool inner_object.new_object = bool end
def new_object?
def new_object? @new_object end
def owner_id
def owner_id @inner_object.owner_id end
def owner_id=(owner_id)
def owner_id=(owner_id) @inner_object.owner_id=(owner_id) end
def pid
def pid @inner_object.pid end
def refresh
Refreshes the object's info from Fedora
def refresh inner_object.load_attributes_from_fedora @datastreams = datastreams_in_fedora.merge(datastreams_in_memory) end
def relationships
Rely on rels_ext datastream to track relationships array
@returns Hash of relationships, as defined by SemanticNode
def relationships return rels_ext.relationships end
def rels_ext
Failing that, attempts to load from Fedora and addst to in-memory datastreams
Tries to grab from in-memory datastreams first
Returns the RELS-EXT Datastream
def rels_ext if !datastreams.has_key?("RELS-EXT") add_datastream(ActiveFedora::RelsExtDatastream.new) end return datastreams["RELS-EXT"] end
def save
Saves a Base object, and any dirty datastreams, then updates
def save #@metadata_is_dirty = false # If it's a new object, set the conformsTo relationship for Fedora CMA if new_object? result = create else result = update end @new_object = false self.update_index if @metadata_is_dirty == true && ENABLE_SOLR_UPDATES @metadata_is_dirty == false return result end
def state
def state @inner_object.state end
def to_param
def to_param self.pid end
def to_solr(solr_doc = Solr::Document.new)
def to_solr(solr_doc = Solr::Document.new) solr_doc << {SOLR_DOCUMENT_ID.to_sym => pid, solr_name(:system_create, :date) => self.create_date, solr_name(:system_modified, :date) => self.modified_date, solr_name(:active_fedora_model, :symbol) => self.class.inspect} datastreams.each_value do |ds| solr_doc = ds.to_solr(solr_doc) if ds.kind_of?(ActiveFedora::MetadataDatastream) || ds.kind_of?(ActiveFedora::RelsExtDatastream) end return solr_doc end
def to_xml(xml=REXML::Document.new("<xml><fields/><content/></xml>"))
def to_xml(xml=REXML::Document.new("<xml><fields/><content/></xml>")) fields_xml = xml.root.elements['fields'] {:id => pid, :system_create_date => self.create_date, :system_modified_date => self.modified_date, :active_fedora_model => self.class.inspect}.each_pair do |attribute_name, value| el = REXML::Element.new(attribute_name.to_s) el.text = value fields_xml << el end datastreams.each_value do |ds| ds.to_xml(fields_xml) if ds.kind_of?(ActiveFedora::MetadataDatastream) || ds.kind_of?(ActiveFedora::RelsExtDatastream) end return xml.to_s end
def update
def update result = Fedora::Repository.instance.save(@inner_object) datastreams_in_memory.each do |k,ds| if ds.dirty? || ds.new_object? if ds.kind_of?(ActiveFedora::MetadataDatastream) || ds.instance_of?(ActiveFedora::RelsExtDatastream) @metadata_is_dirty = true end result = ds.save end end refresh return result end
def update_attributes(params={}, opts={})
or
m.update_attributes({:fubar=>'baz'}, :datastreams=>"my_ds")
use the :datastreams argument like so:
If you want to specify which datastream(s) to update,
are _both_ overwritten.
the object's datastreams. This means DS1.fubar_values and DS2.fubar_values
This will attempt to set the values for any fields named fubar in any of
m.update_attributes(:fubar=>'baz')
Example Usage:
An ActiveRecord-ism to udpate metadata values.
def update_attributes(params={}, opts={}) params.each do |k,v| if v == :delete || v == "" || v == nil v = [] end if opts[:datastreams] ds_array = [] opts[:datastreams].each do |dsname| ds_array << datastreams[dsname] end else ds_array = datastreams.values end ds_array.each do |d| if d.fields[k.to_sym] d.send("#{k}_values=", v) end end end end
def update_index
def update_index SolrService.instance.conn.update(self.to_solr) end
def update_indexed_attributes(params={}, opts={})
m.update_attributes({"fubar"=>{"-1"=>"mork", "0"=>"york", "1"=>"mangle"}}, :datastreams=>["my_ds", "my_other_ds"])
or
m.update_attributes({"fubar"=>{"-1"=>"mork", "0"=>"york", "1"=>"mangle"}}, :datastreams=>"my_ds")
use the :datastreams argument like so:
If you want to specify which datastream(s) to update,
As in update_attributes, this overwrites _all_ available fields by default.
will be overwritten.
An index of -1 will insert a new value. any existing value at the relevant index
This will result in any datastream field of name :name having the value [a,b]
{{:name=>{"0"=>"a","1"=>"b"}}
must look like this :
A convenience method for updating indexed attributes. The passed in hash
def update_indexed_attributes(params={}, opts={}) if opts[:datastreams] ds_array = [] opts[:datastreams].each do |dsname| ds_array << datastreams[dsname] end else ds_array = datastreams.values end result = params.dup params.each do |key,value| result[key] = value.dup ds_array.each do |dstream| if dstream.fields[key.to_sym] aname="#{key}_values" curval = dstream.send("#{aname}") cpv=value.dup#copy this, we'll need the original for the next ds cpv.delete_if do |y,z| if curval[y.to_i] and y.to_i > -1 curval[y.to_i]=z true else false end end cpv.each do |y,z| curval<<z #just append everything left if y == "-1" new_array_index = curval.length - 1 result[key][new_array_index.to_s] = params[key]["-1"] end end curval.delete_if {|x| x == :delete || x == "" || x == nil} dstream.send("#{aname}=", curval) #write it back to the ds end end result[key].delete("-1") end return result end