Skip to content

Commit

Permalink
Added ldp:IndirectContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed May 8, 2015
1 parent 36d4026 commit 7a36f43
Show file tree
Hide file tree
Showing 13 changed files with 367 additions and 64 deletions.
1 change: 1 addition & 0 deletions lib/active_fedora.rb
Expand Up @@ -55,6 +55,7 @@ module ActiveFedora #:nodoc:
autoload_under 'containers' do
autoload :Container
autoload :DirectContainer
autoload :IndirectContainer
end
autoload :Datastream
autoload :Datastreams
Expand Down
36 changes: 35 additions & 1 deletion lib/active_fedora/associations.rb
Expand Up @@ -24,8 +24,10 @@ module Associations
autoload :HasManyAssociation, 'active_fedora/associations/has_many_association'
autoload :BelongsToAssociation, 'active_fedora/associations/belongs_to_association'
autoload :HasAndBelongsToManyAssociation, 'active_fedora/associations/has_and_belongs_to_many_association'
autoload :ContainsAssociation, 'active_fedora/associations/contains_association'
autoload :BasicContainsAssociation, 'active_fedora/associations/basic_contains_association'
autoload :DirectlyContainsAssociation, 'active_fedora/associations/directly_contains_association'
autoload :IndirectlyContainsAssociation, 'active_fedora/associations/indirectly_contains_association'
autoload :ContainsAssociation, 'active_fedora/associations/contains_association'

module Builder
autoload :Association, 'active_fedora/associations/builder/association'
Expand All @@ -37,6 +39,7 @@ module Builder
autoload :HasAndBelongsToMany, 'active_fedora/associations/builder/has_and_belongs_to_many'
autoload :Contains, 'active_fedora/associations/builder/contains'
autoload :DirectlyContains, 'active_fedora/associations/builder/directly_contains'
autoload :IndirectlyContains, 'active_fedora/associations/builder/indirectly_contains'

autoload :Property, 'active_fedora/associations/builder/property'
autoload :SingularProperty, 'active_fedora/associations/builder/singular_property'
Expand Down Expand Up @@ -101,6 +104,37 @@ def directly_contains(name, options={})
Builder::DirectlyContains.build(self, name, { class_name: 'ActiveFedora::File' }.merge(options))
end

# This method is used to declare an ldp:IndirectContainer on a resource
# you must specify an is_member_of_relation or a has_member_relation
#
# @param [String] name the handle to refer to this child as
# @param [Hash] options
# @option options [String] :class_name ('ActiveFedora::File') The name of the class that will represent the contained resources
# @option options [RDF::URI] :has_member_relation the rdf predicate to use for the ldp:hasMemberRelation
# @option options [RDF::URI] :is_member_of_relation the rdf predicate to use for the ldp:isMemberOfRelation
# @option options [RDF::URI] :inserted_content_relation the rdf predicate to use for the ldp:insertedContentRelation
# @option options [String] :through name of a class to represent the interstitial node
# @option options [Symbol] :foreign_key property that points at the remote resource
#
# example:
# class Proxy < ActiveFedora::Base
# belongs_to :proxy_for, predicate: ::RDF::URI.new('http://www.openarchives.org/ore/terms/proxyFor'), class_name: 'ActiveFedora::Base'
# end
#
# class FooHistory < ActiveFedora::Base
# indirectly_contains :files, has_member_relation: RDF::Vocab::ORE.aggregates,
# inserted_content_relation: RDF::Vocab::ORE.proxyFor, class_name: 'Thing',
# through: 'Proxy', foreign_key: :proxy_for
#
# indirectly_contains :other_stuff, is_member_of_relation:
# ::RDF::URI.new("http://example.com/isContainedBy"), class_name: 'Thing',
# through: 'Proxy', foreign_key: :proxy_for
# end
#
def indirectly_contains(name, options={})
Builder::IndirectlyContains.build(self, name, options)
end

def has_many(name, options={})
Builder::HasMany.build(self, name, options)
end
Expand Down
52 changes: 52 additions & 0 deletions lib/active_fedora/associations/basic_contains_association.rb
@@ -0,0 +1,52 @@
module ActiveFedora
module Associations
class BasicContainsAssociation < SingularAssociation #:nodoc:
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
def reader(force_reload = false)
super || build
end

def find_target
reflection.build_association(target_uri).tap do |record|
configure_datastream(record) if reflection.options[:block]
end
end

def target_uri
"#{owner.uri}/#{reflection.name}"
end

private

def raise_on_type_mismatch(record)
return if record.is_a? LoadableFromJson::SolrBackedMetadataFile
super
end

def replace(record)
if record
raise_on_type_mismatch(record)
@updated = true
end

self.target = record
end

def new_record(method, attributes)
record = super
configure_datastream(record)
record
end

def configure_datastream(record)
# If you called has_metadata with a block, pass the block into the File class
if reflection.options[:block].class == Proc
reflection.options[:block].call(record)
end
if record.new_record? && reflection.options[:autocreate]
record.datastream_will_change!
end
end
end
end
end
26 changes: 26 additions & 0 deletions lib/active_fedora/associations/builder/indirectly_contains.rb
@@ -0,0 +1,26 @@
module ActiveFedora::Associations::Builder
class IndirectlyContains < CollectionAssociation #:nodoc:
self.macro = :indirectly_contains
self.valid_options += [:has_member_relation, :is_member_of_relation, :inserted_content_relation, :foreign_key, :through]
self.valid_options -= [:predicate]

def build
reflection = super
configure_dependency
reflection
end

def validate_options
super
if !options[:has_member_relation] && !options[:is_member_of_relation]
raise ArgumentError, "You must specify a predicate for #{name}"
elsif !options[:has_member_relation].kind_of?(RDF::URI) && !options[:is_member_of_relation].kind_of?(RDF::URI)
raise ArgumentError, "Predicate must be a kind of RDF::URI"
end

raise ArgumentError, "Missing :through option" if !options[:through]
raise ArgumentError, "Missing :foreign_key option" if !options[:foreign_key]
end
end
end

51 changes: 11 additions & 40 deletions lib/active_fedora/associations/contains_association.rb
@@ -1,52 +1,23 @@
# This is the parent class of DirectlyContainsAssociation and IndirectlyContainsAssociation
module ActiveFedora
module Associations
class ContainsAssociation < SingularAssociation #:nodoc:
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
def reader(force_reload = false)
super || build
end

def find_target
reflection.build_association(target_uri).tap do |record|
configure_datastream(record) if reflection.options[:block]
end
end
class ContainsAssociation < CollectionAssociation #:nodoc:

def target_uri
"#{owner.uri}/#{reflection.name}"
def reader
@records ||= ContainerProxy.new(self)
end

private
protected

def raise_on_type_mismatch(record)
return if record.is_a? LoadableFromJson::SolrBackedMetadataFile
super
end

def replace(record)
if record
raise_on_type_mismatch(record)
@updated = true
def count_records
load_target.size
end

self.target = record
end

def new_record(method, attributes)
record = super
configure_datastream(record)
record
end

def configure_datastream(record)
# If you called has_metadata with a block, pass the block into the File class
if reflection.options[:block].class == Proc
reflection.options[:block].call(record)
def uri
raise "Can't get uri. Owner isn't saved" if @owner.new_record?
"#{@owner.uri}/#{@reflection.name}"
end
if record.new_record? && reflection.options[:autocreate]
record.datastream_will_change!
end
end
end
end
end

17 changes: 1 addition & 16 deletions lib/active_fedora/associations/directly_contains_association.rb
@@ -1,6 +1,6 @@
module ActiveFedora
module Associations
class DirectlyContainsAssociation < CollectionAssociation #:nodoc:
class DirectlyContainsAssociation < ContainsAssociation #:nodoc:

def insert_record(record, force = true, validate = true)
container.save!
Expand All @@ -11,10 +11,6 @@ def insert_record(record, force = true, validate = true)
end
end

def reader
@records ||= ContainerProxy.new(self)
end

def find_target
query_node = if container_predicate = options[:has_member_relation]
owner
Expand All @@ -40,21 +36,10 @@ def container

protected

def count_records
load_target.size
end

def initialize_attributes(record) #:nodoc:
record.uri = ActiveFedora::Base.id_to_uri(container.mint_id)
set_inverse_instance(record)
end

private

def uri
raise "Can't get uri. Owner isn't saved" if @owner.new_record?
"#{@owner.uri}/#{@reflection.name}"
end
end
end
end
68 changes: 68 additions & 0 deletions lib/active_fedora/associations/indirectly_contains_association.rb
@@ -0,0 +1,68 @@
module ActiveFedora
module Associations
class IndirectlyContainsAssociation < ContainsAssociation #:nodoc:

def insert_record(record, force = true, validate = true)
container.save!
if force
record.save!
else
return false unless record.save(validate: validate)
end

save_through_record(record)

return true
end

def find_target
if container_predicate = options[:has_member_relation]
uris = owner.resource.query(predicate: container_predicate).map { |r| r.object.to_s }
uris.map { |object_uri| klass.find(klass.uri_to_id(object_uri)) }
else
# TODO this is a lot of reads. Avoid this path
container_predicate = ::RDF::Vocab::LDP.contains
proxy_uris = container.resource.query(predicate: container_predicate).map { |r| r.object.to_s }
proxy_uris.map { |uri| proxy_class.find(proxy_class.uri_to_id(uri))[options[:foreign_key]] }
end


end

def container
@container ||= begin
IndirectContainer.find_or_initialize(ActiveFedora::Base.uri_to_id(uri)).tap do |container|
container.parent = @owner
container.has_member_relation = Array(options[:has_member_relation])
container.is_member_of_relation = Array(options[:is_member_of_relation])
container.inserted_content_relation = Array(options[:inserted_content_relation])
end
end
end

protected

def initialize_attributes(record) #:nodoc:
#record.uri = ActiveFedora::Base.id_to_uri(container.mint_id)
# set_inverse_instance(record)
end

private

def save_through_record(record)
build_proxy_node({}) do |node|
node[options[:foreign_key]] = record
node.save
end
end

def build_proxy_node(attributes, &block)
proxy_class.new({ id: container.mint_id }.merge(attributes), &block)
end

def proxy_class
@proxy_class ||= options[:through].constantize
end
end
end
end
2 changes: 1 addition & 1 deletion lib/active_fedora/attached_files.rb
Expand Up @@ -124,7 +124,7 @@ def undeclared_files
private
def create_singleton_association(file_path)
self.undeclared_files << file_path.to_sym
association = Associations::ContainsAssociation.new(self, Reflection::AssociationReflection.new(:contains, file_path, {class_name: 'ActiveFedora::File'}, self.class))
association = Associations::BasicContainsAssociation.new(self, Reflection::AssociationReflection.new(:contains, file_path, {class_name: 'ActiveFedora::File'}, self.class))
@association_cache[file_path.to_sym] = association

self.singleton_class.send :define_method, accessor_name(file_path) do
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fedora/autosave_association.rb
Expand Up @@ -75,7 +75,7 @@ module ActiveFedora
module AutosaveAssociation
extend ActiveSupport::Concern

ASSOCIATION_TYPES = %w{ HasMany BelongsTo HasAndBelongsToMany DirectlyContains }
ASSOCIATION_TYPES = %w{ HasMany BelongsTo HasAndBelongsToMany DirectlyContains IndirectlyContains}

module AssociationBuilderExtension #:nodoc:
def self.included(base)
Expand Down
2 changes: 0 additions & 2 deletions lib/active_fedora/containers/direct_container.rb
@@ -1,7 +1,5 @@
module ActiveFedora
class DirectContainer < Container
type ::RDF::Vocab::LDP.DirectContainer


end
end
7 changes: 7 additions & 0 deletions lib/active_fedora/containers/indirect_container.rb
@@ -0,0 +1,7 @@
module ActiveFedora
class IndirectContainer < Container
type ::RDF::Vocab::LDP.IndirectContainer

property :inserted_content_relation, predicate: ::RDF::Vocab::LDP.insertedContentRelation
end
end
8 changes: 5 additions & 3 deletions lib/active_fedora/reflection.rb
Expand Up @@ -10,7 +10,7 @@ module Reflection # :nodoc:
module ClassMethods
def create_reflection(macro, name, options, active_fedora)
klass = case macro
when :has_many, :belongs_to, :has_and_belongs_to_many, :contains, :directly_contains
when :has_many, :belongs_to, :has_and_belongs_to_many, :contains, :directly_contains, :indirectly_contains
AssociationReflection
when :rdf, :singular_rdf
RDFPropertyReflection
Expand Down Expand Up @@ -167,7 +167,7 @@ class AssociationReflection < MacroReflection #:nodoc:

def initialize(macro, name, options, active_fedora)
super
@collection = [:has_many, :has_and_belongs_to_many, :directly_contains].include?(macro)
@collection = [:has_many, :has_and_belongs_to_many, :directly_contains, :indirectly_contains].include?(macro)
end


Expand Down Expand Up @@ -247,7 +247,7 @@ def validate?
def association_class
case macro
when :contains
Associations::ContainsAssociation
Associations::BasicContainsAssociation
when :belongs_to
Associations::BelongsToAssociation
when :has_and_belongs_to_many
Expand All @@ -260,6 +260,8 @@ def association_class
Associations::RDF
when :directly_contains
Associations::DirectlyContainsAssociation
when :indirectly_contains
Associations::IndirectlyContainsAssociation
end
end

Expand Down

0 comments on commit 7a36f43

Please sign in to comment.