module ActiveFedora
module Delegating
extend ActiveSupport::Concern
# Calling inspect may trigger a bunch of loads, but it's mainly for debugging, so no worries.
def inspect
values = self.class.delegate_registry.map {|r| "#{r}:#{send(r).inspect}"}
"#<#{self.class} pid:\"#{pretty_pid}\", #{values.join(', ')}>"
end
def [](key)
array_reader(key)
end
def []=(key, value)
array_setter(key, value)
end
private
def array_reader(field)
instance_exec(&self.class.delegates[field][:reader])
end
def array_setter(field, args)
instance_exec(args, &self.class.delegates[field][:setter])
end
module ClassMethods
def delegates
@delegates ||= {}
end
def delegates= val
@delegates = val
end
# Provides a delegate class method to expose methods in metadata streams
# as member of the base object. Pass the target datastream via the
# :to argument. If you want to return a unique result, (e.g. string
# instead of an array) set the :unique argument to true.
#
# The optional :at argument provides a terminology that the delegate will point to.
#
# class Foo < ActiveFedora::Base
# has_metadata :name => "descMetadata", :type => MyDatastream
#
# delegate :field1, :to=>"descMetadata", :unique=>true
# delegate :field2, :to=>"descMetadata", :at=>[:term1, :term2]
# end
#
# foo = Foo.new
# foo.field1 = "My Value"
# foo.field1 # => "My Value"
# foo.field2 # => NoMethodError: undefined method `field2' for #
def delegate(*methods)
fields = methods.dup
options = fields.pop
unless options.is_a?(Hash) && to = options[:to]
raise ArgumentError, "Target is required"
end
if ds_specs.has_key? to.to_s
create_delegate_reader(fields.first, options)
create_delegate_setter(fields.first, options)
else
super(*methods)
end
end
def delegate_registry
self.delegates.keys
end
# Allows you to delegate multiple terminologies to the same datastream, instead
# having to call the method each time for each term. The target datastream is the
# first argument, followed by an array of the terms that will point to that
# datastream. Terms must be a single value, ie. :field and not [:term1, :term2].
# This is best accomplished by refining your OM terminology using :ref or :proxy
# methods to reduce nested terms down to one.
#
# class Foo < ActiveFedora::Base
# has_metadata :name => "descMetadata", :type => MyDatastream
#
# delegate_to :descMetadata, [:field1, :field2]
# end
#
# foo = Foo.new
# foo.field1 = "My Value"
# foo.field1 # => "My Value"
# foo.field2 # => NoMethodError: undefined method `field2' for #
def delegate_to(datastream,fields,args={})
fields.each do |f|
args.merge!({:to=>datastream})
create_delegate_reader(f, args)
create_delegate_setter(f, args)
end
end
private
def create_delegate_reader(field, args)
self.delegates[field] ||= {}
self.delegates[field][:reader] = lambda do
ds = self.send(args[:to])
if ds.kind_of?(ActiveFedora::RDFDatastream)
ds.send(field)
else
terminology = args[:at] || [field]
ds.send(:term_values, *terminology)
end
end
define_method field do
val = self[field]
args[:unique] ? val.first : val
end
end
def create_delegate_setter(field, args)
self.delegates[field] ||= {}
self.delegates[field][:setter] = lambda do |v|
ds = self.send(args[:to])
if ds.kind_of?(ActiveFedora::RDFDatastream)
ds.send("#{field}=", v)
else
terminology = args[:at] || [field]
ds.send(:update_indexed_attributes, {terminology => v})
end
end
define_method "#{field}=".to_sym do |v|
self[field]=v
end
end
end
end
end