module ActiveFedora
# A legacy class that creates and updates simple xml documents
# For much greater flexibility, use {ActiveFedora::NokogiriDatastream} instead.
# @example The simple, flat xml structure used by these datastreams
#
# Foo
# Bar
#
class MetadataDatastream < NokogiriDatastream
# .to_solr (among other things) is provided by ActiveFedora::MetadataDatastreamHelper
include ActiveFedora::MetadataDatastreamHelper
def initialize(digital_object, dsid)
ActiveSupport::Deprecation.warn("MetadataDatastream is deprecated and will be removed in a future release. Create a NokogiriDatastream to structure your data")
super
end
def to_solr(solr_doc = Hash.new) # :nodoc:
fields.each do |field_key, field_info|
if field_info.has_key?(:values) && !field_info[:values].nil?
field_symbol = ActiveFedora::SolrService.solr_name(field_key, field_info[:type])
values = field_info[:values]
values = [values] unless values.respond_to? :each
values.each do |val|
::Solrizer::Extractor.insert_solr_field_value(solr_doc, field_symbol, val )
end
end
end
return solr_doc
end
# ** EXPERIMENTAL **
#
# This is utilized by ActiveFedora::Base.load_instance_from_solr to set
# metadata values in this object using the Solr document passed in.
# Any keys in the solr document that map to a metadata field key within a MetadataDatastream object
# are set to the corresponding value. Any others are ignored. ActiveFedora::SolrService.solr_name
# is used to map solr key to field key name.
#
# ====Warning
# Solr must be synchronized with data in Fedora.
# @content is initialized to the empty document template to satisfy #ensure_xml_loaded
# (called from #update_attributes and #update_indexed_attributes)
def from_solr(solr_doc)
@content = self.to_xml
self.xml_loaded = true
fields.each do |field_key, field_info|
field_symbol = ActiveFedora::SolrService.solr_name(field_key, field_info[:type])
value = (solr_doc[field_symbol].nil? ? solr_doc[field_symbol.to_s]: solr_doc[field_symbol])
unless value.nil?
if value.is_a? Array
update_attributes({field_key=>value})
else
update_indexed_attributes({field_key=>{0=>value}})
end
end
end
end
def update_attributes(params={},opts={})
result = params.dup
params.each do |k,v|
if v == :delete || v == "" || v == nil
v = []
end
if self.fields.has_key?(k.to_sym)
result[k] = set_value(k, v)
else
result.delete(k)
end
end
return result
end
# An ActiveRecord-ism to udpate metadata values.
#
# The passed in hash must look like this :
# {:name=>{"0"=>"a","1"=>"b"}}
#
# This will attempt to set the values for any field named fubar in the datastream.
# If there is no field by that name, it returns an empty hash and doesn't change the object at all.
# If there is a field by that name, it will set the values for field of name :name having the value [a,b]
# and it returns a hash with the field name, value index, and the value as it was set.
#
# An index of -1 will insert a new value. any existing value at the relevant index
# will be overwritten.
#
# As in update_attributes, this overwrites _all_ available fields by default.
#
# Example Usage:
#
# ds.update_attributes({:myfield=>{"0"=>"a","1"=>"b"},:myotherfield=>{"-1"=>"c"}})
#
def update_indexed_attributes(params={}, opts={})
ensure_xml_loaded
##FIX this bug, it should delete it from a copy of params in case passed to another datastream for update since this will modify params
##for subsequent calls if updating more than one datastream in a single update_indexed_attributes call
current_params = params.clone
# remove any fields from params that this datastream doesn't recognize
current_params.delete_if do |field_name,new_values|
if field_name.kind_of?(Array) then field_name = field_name.first end
!self.fields.include?(field_name.to_sym)
end
result = current_params.dup
current_params.each do |field_name,new_values|
if field_name.kind_of?(Array) then field_name = field_name.first end
##FIX this bug, it should delete it from a copy of params in case passed to another datastream for update
#if field does not exist just skip it
next if !self.fields.include?(field_name.to_sym)
field_accessor_method = "#{field_name}_values"
if new_values.kind_of?(Hash)
result[field_name] = new_values.dup
current_values = instance_eval(field_accessor_method)
# current_values = get_values(field_name) # for some reason this leaves current_values unset?
new_values.delete_if do |y,z|
if current_values[y.to_i] and y.to_i > -1
current_values[y.to_i]=z
true
else
false
end
end
new_values.keys.sort { |a,b| a.to_i <=> b.to_i }.each do |y|
z = new_values[y]
result[field_name].delete(y)
current_values<values}
end
self.dirty = true
end
return result
end
def get_values(field_name, default=[])
ensure_xml_loaded
field_accessor_method = "#{field_name}_values"
if respond_to? field_accessor_method
values = self.send(field_accessor_method)
else
values = []
end
if values.empty?
if default.nil?
return default
else
return default
end
else
return values
end
end
def set_value(field_name, values)
ensure_xml_loaded
field_accessor_method = "#{field_name}_values="
if respond_to? field_accessor_method
values = self.send(field_accessor_method, values)
return self.send("#{field_name}_values")
end
end
# Populate a MetadataDatastream object based on the "datastream" node from a FOXML file
# @param [ActiveFedora::Datastream] tmpl the Datastream object that you are building
# @param [Nokogiri::XML::Node] node the "foxml:datastream" node from a FOXML file. Assumes that the content of this datastream is that of an ActiveFedora MetadataDatastream (...)
def self.from_xml(xml, tmpl) # :nodoc:
node = Nokogiri::XML::Document.parse(xml)
node.xpath("fields/node()").each do |f|
tmpl.send("#{f.name}_append", f.text) unless f.class == Nokogiri::XML::Text
end
tmpl.send(:dirty=, false)
tmpl
end
def to_xml(xml = Nokogiri::XML::Document.parse("")) #:nodoc:
if xml.instance_of?(Nokogiri::XML::Builder)
xml_insertion_point = xml.doc.root
elsif xml.instance_of?(Nokogiri::XML::Document)
xml_insertion_point = xml.root
else
xml_insertion_point = xml
end
builder = Nokogiri::XML::Builder.with(xml_insertion_point) do |xml|
fields.each do |field,field_info|
element_attrs = field_info[:element_attrs].nil? ? {} : field_info[:element_attrs]
values = field_info[:values]
values = [values] unless values.respond_to? :each
values.each do |val|
builder_arg = "xml.#{field}(val, element_attrs)"
eval(builder_arg)
end
end
end
return builder.to_xml
end
# This method generates the various accessor and mutator methods on self for the datastream metadata attributes.
# each field will have the 3 magic methods:
# name_values=(arg)
# name_values
# name_append(arg)
#
#
# Calling any of the generated methods marks self as dirty.
#
# 'tupe' is a datatype, currently :string, :text and :date are supported.
#
# opts is an options hash, which will affect the generation of the xml representation of this datastream.
#
# Currently supported modifiers:
# For +QualifiedDublinCorDatastreams+:
# :element_attrs =>{:foo=>:bar} - hash of xml element attributes
# :xml_node => :nodename - The xml node to be used to represent this object (in dcterms namespace)
# :encoding=>foo, or encodings_scheme - causes an xsi:type attribute to be set to 'foo'
# :multiple=>true - mark this field as a multivalue field (on by default)
#
#At some point, these modifiers will be ported up to work for any +ActiveFedora::MetadataDatastream+.
#
#There is quite a good example of this class in use in spec/examples/oral_history.rb
#
#!! Careful: If you declare two fields that correspond to the same xml node without any qualifiers to differentiate them,
#you will end up replicating the values in the underlying datastream, resulting in mysterious dubling, quadrupling, etc.
#whenever you edit the field's values.
def field(name, tupe, opts={})
#TODO add term to terminology
@fields[name.to_s.to_sym]={:type=>tupe, :values=>[]}.merge(opts)
#eval <<-EOS
(class << self; self; end).class_eval do
define_method "#{name}_values=".to_sym do |arg|
ensure_xml_loaded
@fields["#{name.to_s}".to_sym][:values]=[arg].flatten
self.dirty=true
end
define_method "#{name}_values".to_sym do
ensure_xml_loaded
@fields["#{name}".to_sym][:values]
end
define_method "#{name}_append".to_sym do |arg|
ensure_xml_loaded
@fields["#{name}".to_sym][:values] << arg
self.dirty =true
end
end
end
end
end