Skip to content

Commit

Permalink
Refactoring DelegateAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
awead committed Jun 23, 2015
1 parent e79b12b commit 03e124a
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 112 deletions.
6 changes: 6 additions & 0 deletions lib/active_fedora.rb
Expand Up @@ -59,6 +59,12 @@ module ActiveFedora #:nodoc:
autoload :Datastream
autoload :Datastreams
autoload :DelegatedAttribute
autoload_under 'attributes' do
autoload :StreamAttribute
autoload :ActiveTripleAttribute
autoload :OmAttribute
autoload :RdfDatastreamAttribute
end
autoload :Fedora
autoload :FedoraAttributes
autoload :File
Expand Down
36 changes: 27 additions & 9 deletions lib/active_fedora/attributes.rb
Expand Up @@ -177,7 +177,7 @@ def property name, properties={}, &block
elsif properties.key?(:delegate_to)
define_delegated_accessor([name], properties.delete(:delegate_to), properties.reverse_merge(multiple: true), &block)
else
raise "You must provide `:datastream' or `:predicate' options to property"
raise "You must provide `:delegate_to' or `:predicate' options to property"
end
end

Expand All @@ -186,7 +186,7 @@ def property name, properties={}, &block
def define_active_triple_accessor(name, properties, &block)
warn_duplicate_predicates name, properties
properties = { multiple: true }.merge(properties)
find_or_create_defined_attribute(name, nil, properties)
find_or_create_defined_attribute(name, ActiveTripleAttribute, properties)
raise ArgumentError, "#{name} is a keyword and not an acceptable property name." if protected_property_name? name
reflection = ActiveFedora::Attributes::PropertyBuilder.build(self, name, properties, &block)
ActiveTriples::Reflection.add_reflection self, name, reflection
Expand All @@ -197,14 +197,17 @@ def define_active_triple_accessor(name, properties, &block)
def define_delegated_accessor(fields, delegate_target, options, &block)
define_attribute_methods fields
fields.each do |f|
klass = datastream_class_for_name(delegate_target)
attribute_properties = options.merge(delegate_target: delegate_target, klass: klass)
find_or_create_defined_attribute f, attribute_class(klass), attribute_properties

create_attribute_reader(f, delegate_target, options)
create_attribute_setter(f, delegate_target, options)
add_attribute_indexing_config(f, &block) if block_given?
end
end

def add_attribute_indexing_config(name, &block)
# TODO the hash can be initalized to return on of these
index_config[name] ||= ActiveFedora::Indexing::Map::IndexObject.new &block
end

Expand All @@ -215,31 +218,46 @@ def warn_duplicate_predicates new_name, new_properties
end
end

def find_or_create_defined_attribute(field, dsid, args)
delegated_attributes[field] ||= DelegatedAttribute.new(field, dsid, datastream_class_for_name(dsid), args)
# @param [Symbol] field the field to find or create
# @param [Class] klass the class to use to delegate the attribute (e.g.
# ActiveTripleAttribute, OmAttribute, or RdfDatastreamAttribute)
# @param [Hash] args
# @option args [String] :delegate_target the path to the delegate
# @option args [Class] :klass the class to create
# @option args [true,false] :multiple (false) true for multi-value fields
# @option args [Array<Symbol>] :at path to a deep node
# @return [DelegatedAttribute] the found or created attribute
def find_or_create_defined_attribute(field, klass, args)
delegated_attributes[field] ||= klass.new(field, args)
end

# @param [String] dsid the datastream id
# @return [Class] the class of the datastream
def datastream_class_for_name(dsid)
reflection = reflect_on_association(dsid)
reflection = reflect_on_association(dsid.to_sym)
reflection ? reflection.klass : ActiveFedora::File
end

def create_attribute_reader(field, dsid, args)
find_or_create_defined_attribute(field, dsid, args)

define_method field do |*opts|
array_reader(field, *opts)
end
end

def create_attribute_setter(field, dsid, args)
find_or_create_defined_attribute(field, dsid, args)
define_method "#{field}=".to_sym do |v|
self[field]=v
end
end

def attribute_class(klass)
if klass < ActiveFedora::RDFDatastream
RdfDatastreamAttribute
else
OmAttribute
end
end

end
end
end
17 changes: 17 additions & 0 deletions lib/active_fedora/attributes/active_triple_attribute.rb
@@ -0,0 +1,17 @@
module ActiveFedora

# Attributes delegated to ActiveTriples. Allows ActiveFedora to track all attributes consistently.
#
# @example
# class Book < ActiveFedora::Base
# property :title, predicate: ::RDF::DC.title
# property :author, predicate: ::RDF::DC.creator
# end
#
# Book.attribute_names
# => ["title", "author"]

class ActiveTripleAttribute < DelegatedAttribute
end

end
29 changes: 29 additions & 0 deletions lib/active_fedora/attributes/om_attribute.rb
@@ -0,0 +1,29 @@
module ActiveFedora

# Class for attributes that are delegated to an OmDatastream

class OmAttribute < StreamAttribute

# @param [ActiveFedora::Base] obj the object that has the attribute
# @param [Object] v value to write to the attribute
def writer(obj, v)
ds = file_for_attribute(obj, delegate_target)
obj.mark_as_changed(field) if obj.value_has_changed?(field, v)
terminology = at || [field]
ds.send(:update_indexed_attributes, {terminology => v})
end

# @param [ActiveFedora::Base] obj the object that has the attribute
# @param [Object] opts extra options that are passed to the target reader
def reader(obj, *opts)
ds = file_for_attribute(obj, delegate_target)
terminology = at || [field]
if terminology.length == 1 && opts.present?
ds.send(terminology.first, *opts)
else
ds.send(:term_values, *terminology)
end
end

end
end
47 changes: 47 additions & 0 deletions lib/active_fedora/attributes/rdf_datastream_attribute.rb
@@ -0,0 +1,47 @@
module ActiveFedora

# Class for attributes that are delegated to a RDFDatastream

class RdfDatastreamAttribute < StreamAttribute

# @param [ActiveFedora::Base] obj the object that has the attribute
# @param [Object] v value to write to the attribute
def writer(obj, v)
node = file_for_attribute(obj, delegate_target)
obj.mark_as_changed(field) if obj.value_has_changed?(field, v)
term = if at
vals = at.dup
while vals.length > 1
node = node.send(vals.shift)
node = node.build if node.empty?
node = node.first
end
vals.first
else
field
end
node.send("#{term}=", v)
end

# @param [ActiveFedora::Base] obj the object that has the attribute
def reader(obj)
node = file_for_attribute(obj, delegate_target)
term = if at
vals = at.dup
while vals.length > 1
node = node.send(vals.shift)
node = if node.empty?
node.build
else
node.first
end
end
vals.first
else
field
end
node.send(term)
end

end
end
46 changes: 46 additions & 0 deletions lib/active_fedora/attributes/stream_attribute.rb
@@ -0,0 +1,46 @@
module ActiveFedora

# Abstract class for attributes that are delegated to a serialized representation such as a NonRDFSource
#
# @abstract
# @attr [String] delegate_target
# @attr [String] at
# @attr [String] target_class

class StreamAttribute < DelegatedAttribute

attr_accessor :delegate_target, :at, :target_class

# @param [Symbol] field the field to find or create
# @param [Hash] args
# @option args [String] :delegate_target the path to the delegate
# @option args [Class] :klass the class to create
# @option args [true,false] :multiple (false) true for multi-value fields
# @option args [Array<Symbol>] :at path to a deep node
def initialize(field, args={})
super
self.delegate_target = args.fetch(:delegate_target)
self.target_class = args.fetch(:klass)
self.at = args.fetch(:at, nil)
end

# Gives the primary solr name for a column. If there is more than one indexer on the field definition, it gives the first
def primary_solr_name
@datastream ||= target_class.new
raise NoMethodError, "the file '#{target_class}' doesn't respond to 'primary_solr_name'" unless @datastream.respond_to?(:primary_solr_name)
@datastream.primary_solr_name(field, delegate_target)
end

def type
raise NoMethodError, "the file '#{target_class}' doesn't respond to 'type'" unless target_class.respond_to?(:type)
target_class.type(field)
end

private

def file_for_attribute(obj, delegate_target)
obj.attached_files[delegate_target] || raise(ArgumentError, "Undefined file: `#{delegate_target}' in property #{field}")
end

end
end
101 changes: 4 additions & 97 deletions lib/active_fedora/delegated_attribute.rb
Expand Up @@ -2,105 +2,12 @@ module ActiveFedora
# Represents the mapping between a model attribute and a field in a datastream
class DelegatedAttribute

attr_accessor :dsid, :field, :datastream_class, :at, :multiple
attr_accessor :field, :multiple

def initialize(field, dsid, datastream_class, args={})
def initialize(field, args={})
self.field = field
self.dsid = dsid
self.datastream_class = datastream_class
self.multiple = args[:multiple].nil? ? false : args[:multiple]
self.at = args[:at]
self.multiple = args.fetch(:multiple, false)
end

# Gives the primary solr name for a column. If there is more than one indexer on the field definition, it gives the first
def primary_solr_name
@datastream ||= datastream_class.new
if @datastream.respond_to?(:primary_solr_name)
@datastream.primary_solr_name(field, dsid)
else
raise NoMethodError, "the datastream '#{datastream_class}' doesn't respond to 'primary_solr_name'"
end
end

def type
if datastream_class.respond_to?(:type)
datastream_class.type(field)
else
raise NoMethodError, "the datastream '#{datastream_class}' doesn't respond to 'type'"
end
end

def writer(obj, v)
ds = datastream_for_attribute(obj, dsid)
obj.mark_as_changed(field) if obj.value_has_changed?(field, v)
if ds.kind_of?(ActiveFedora::RDFDatastream)
write_rdf(ds, v)
else
write_om(ds, v)
end
end

def reader(obj, *opts)
ds = datastream_for_attribute(obj, dsid)
if ds.kind_of?(ActiveFedora::RDFDatastream)
read_rdf(ds)
else
read_om(ds, *opts)
end
end

private

def write_om(ds, v)
terminology = at || [field]
ds.send(:update_indexed_attributes, {terminology => v})
end

def read_om(ds, *opts)
terminology = at || [field]
if terminology.length == 1 && opts.present?
ds.send(terminology.first, *opts)
else
ds.send(:term_values, *terminology)
end
end

def write_rdf(node, v)
term = if at
vals = at.dup
while vals.length > 1
node = node.send(vals.shift)
node = node.build if node.empty?
node = node.first
end
vals.first
else
field
end
node.send("#{term}=", v)
end

def read_rdf(node)
term = if at
vals = at.dup
while vals.length > 1
node = node.send(vals.shift)
node = if node.empty?
node.build
else
node.first
end
end
vals.first
else
field
end
node.send(term)
end

def datastream_for_attribute(obj, dsid)
obj.attached_files[dsid] || raise(ArgumentError, "Undefined datastream id: `#{dsid}' in has_attributes")
end


end
end
12 changes: 6 additions & 6 deletions spec/unit/attributes_spec.rb
Expand Up @@ -407,12 +407,14 @@ class BarHistory4 < ActiveFedora::Base

subject { BarHistory4.new }

it "should raise an error on get" do
expect {subject.description}.to raise_error(ArgumentError, "Undefined datastream id: `rdfish' in has_attributes")
let(:error_message) { "Undefined file: `rdfish' in property description" }

it "raises an error on get" do
expect { subject.description }.to raise_error(ArgumentError, error_message)
end

it "should raise an error on set" do
expect { subject.description = ['Neat'] }.to raise_error(ArgumentError, "Undefined datastream id: `rdfish' in has_attributes")
it "raises an error on set" do
expect { subject.description = ['Neat'] }.to raise_error(ArgumentError, error_message)
end

describe ".datastream_class_for_name" do
Expand Down Expand Up @@ -520,5 +522,3 @@ class BarHistory4 < ActiveFedora::Base
end
end
end


0 comments on commit 03e124a

Please sign in to comment.