Skip to content

Commit

Permalink
Add direct containers
Browse files Browse the repository at this point in the history
You can use the containers by calling:

class FooHistory < ActiveFedora::Base
   directly_contains :files, predicate:
   ::RDF::URI.new("http://example.com/hasFiles"), class_name: 'Thing'
end

if you don't specify a class_name it will default to ActiveFedora::File
  • Loading branch information
jcoyne committed May 6, 2015
1 parent 2c6f867 commit 8f5bde7
Show file tree
Hide file tree
Showing 15 changed files with 280 additions and 15 deletions.
4 changes: 4 additions & 0 deletions lib/active_fedora.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ module ActiveFedora #:nodoc:
autoload :FedoraIdTranslator
autoload :FedoraUriTranslator
end
autoload_under 'containers' do
autoload :Container
autoload :DirectContainer
end
autoload :Datastream
autoload :Datastreams
autoload :DelegatedAttribute
Expand Down
20 changes: 20 additions & 0 deletions lib/active_fedora/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ module Associations
autoload :SingularRDF, 'active_fedora/associations/singular_rdf'
autoload :CollectionAssociation, 'active_fedora/associations/collection_association'
autoload :CollectionProxy, 'active_fedora/associations/collection_proxy'
autoload :ContainerProxy, 'active_fedora/associations/container_proxy'

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 :DirectlyContainsAssociation, 'active_fedora/associations/directly_contains_association'

module Builder
autoload :Association, 'active_fedora/associations/builder/association'
Expand All @@ -34,6 +36,7 @@ module Builder
autoload :HasMany, 'active_fedora/associations/builder/has_many'
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 :Property, 'active_fedora/associations/builder/property'
autoload :SingularProperty, 'active_fedora/associations/builder/singular_property'
Expand Down Expand Up @@ -77,6 +80,23 @@ def association_instance_set(name, association)

module ClassMethods

# This method is used to declare an ldp:DirectContainer on a resource
#
# @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] :predicate required the rdf predicate to use for the ldp:hasMemberRelation
#
# example:
# class FooHistory < ActiveFedora::Base
# directly_contains :files, predicate:
# ::RDF::URI.new("http://example.com/hasFiles"), class_name: 'Thing'
# end
#
def directly_contains(name, options={})
Builder::DirectlyContains.build(self, name, { class_name: 'ActiveFedora::File' }.merge(options))
end

def has_many(name, options={})
Builder::HasMany.build(self, name, options)
end
Expand Down
21 changes: 21 additions & 0 deletions lib/active_fedora/associations/builder/directly_contains.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module ActiveFedora::Associations::Builder
class DirectlyContains < CollectionAssociation #:nodoc:
self.macro = :directly_contains

def build
reflection = super
configure_dependency
reflection
end

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

9 changes: 9 additions & 0 deletions lib/active_fedora/associations/container_proxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module ActiveFedora
module Associations
class ContainerProxy < CollectionProxy
def initialize(association)
@association = association
end
end
end
end
2 changes: 1 addition & 1 deletion lib/active_fedora/associations/contains_association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def reader(force_reload = false)
def find_target
reflection.build_association(target_uri).tap do |record|
configure_datastream(record) if reflection.options[:block]
end
end
end

def target_uri
Expand Down
54 changes: 54 additions & 0 deletions lib/active_fedora/associations/directly_contains_association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module ActiveFedora
module Associations
class DirectlyContainsAssociation < CollectionAssociation #:nodoc:

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

return true
end

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

def find_target
uris = owner.resource.query(predicate: options[:predicate]).map { |r| r.object.to_s }
if klass <= ActiveFedora::File # a subclass of file
uris.map { |uri| klass.new(uri) }
else
uris.map { |uri| klass.find(klass.uri_to_id(uri)) }
end
end

def container
@container ||= begin
DirectContainer.find_or_initialize(ActiveFedora::Base.uri_to_id(uri)).tap do |container|
container.parent = @owner
container.member_relation = [@reflection.predicate]
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 uri
raise "Can't get uri. Owner isn't saved" if @owner.new_record?
"#{@owner.uri}/#{@reflection.name}"
end
end
end
end
2 changes: 1 addition & 1 deletion lib/active_fedora/autosave_association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ module ActiveFedora
module AutosaveAssociation
extend ActiveSupport::Concern

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

module AssociationBuilderExtension #:nodoc:
def self.included(base)
Expand Down
28 changes: 28 additions & 0 deletions lib/active_fedora/containers/container.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module ActiveFedora
class Container < ActiveFedora::Base

property :membership_resource, predicate: ::RDF::Vocab::LDP.membershipResource
property :member_relation, predicate: ::RDF::Vocab::LDP.hasMemberRelation

def parent
@parent || raise("Parent hasn't been set on #{self.class}")
end

def parent=(parent)
@parent = parent
self.membership_resource = [::RDF::URI(parent.uri)]
end

def mint_id
"#{id}/#{SecureRandom.uuid}"
end

def self.find_or_initialize(id)
find(id)
rescue ActiveFedora::ObjectNotFoundError
new(id)
end
end
end


7 changes: 7 additions & 0 deletions lib/active_fedora/containers/direct_container.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module ActiveFedora
class DirectContainer < Container
type ::RDF::Vocab::LDP.DirectContainer


end
end
15 changes: 15 additions & 0 deletions lib/active_fedora/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ def initialize(parent_or_url_or_hash = nil, path=nil, options={})
@attributes = {}.with_indifferent_access
end

def ==(comparison_object)
comparison_object.equal?(self) ||
(comparison_object.instance_of?(self.class) &&
comparison_object.uri == uri &&
!comparison_object.new_record?)
end

def ldp_source
@ldp_source || raise("NO source")
end
Expand All @@ -66,6 +73,10 @@ def new_record?
!@exists && ldp_source.new?
end

def destroyed?
false
end

def uri= uri
@ldp_source = Ldp::Resource::BinarySource.new(ldp_connection, uri, '', ActiveFedora.fedora.host + ActiveFedora.fedora.base_path)
end
Expand Down Expand Up @@ -253,6 +264,10 @@ def save(*)
changed_attributes.clear
end

def save!(*attrs)
save(*attrs)
end

def retrieve_content
ldp_source.get.body
end
Expand Down
6 changes: 4 additions & 2 deletions lib/active_fedora/reflection.rb
Original file line number Diff line number Diff line change
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
when :has_many, :belongs_to, :has_and_belongs_to_many, :contains, :directly_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].include?(macro)
@collection = [:has_many, :has_and_belongs_to_many, :directly_contains].include?(macro)
end


Expand Down Expand Up @@ -258,6 +258,8 @@ def association_class
Associations::SingularRDF
when :rdf
Associations::RDF
when :directly_contains
Associations::DirectlyContainsAssociation
end
end

Expand Down
8 changes: 4 additions & 4 deletions spec/integration/collection_association_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,20 @@ class Book < ActiveFedora::Base
before do
class Item < ActiveFedora::Base
end
class Container < ActiveFedora::Base
class SpecContainer < ActiveFedora::Base
has_many :items
end
end
after do
Object.send(:remove_const, :Item)
Object.send(:remove_const, :Container)
Object.send(:remove_const, :SpecContainer)
end

let(:instance) { Container.new }
let(:instance) { SpecContainer.new }
subject { instance.items }

it "raises an error" do
expect { subject }.to raise_error "No :inverse_of or :predicate attribute was set or could be inferred for has_many :items on Container"
expect { subject }.to raise_error "No :inverse_of or :predicate attribute was set or could be inferred for has_many :items on SpecContainer"
end
end

Expand Down
6 changes: 3 additions & 3 deletions spec/integration/complex_rdf_datastream_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class EbuCore < RDF::Vocabulary("http://www.ebu.ch/metadata/ontologies/ebucore#"
property :title
end

class Container < ActiveFedora::Base
class SpecContainer < ActiveFedora::Base
contains :info, class_name: 'SpecDatastream'
end

Expand All @@ -203,10 +203,10 @@ class Program < ActiveTriples::Resource

after(:each) do
Object.send(:remove_const, :SpecDatastream)
Object.send(:remove_const, :Container)
Object.send(:remove_const, :SpecContainer)
end

let(:parent) { Container.new id: '124' }
let(:parent) { SpecContainer.new id: '124' }
let (:file) { parent.info }


Expand Down
Loading

0 comments on commit 8f5bde7

Please sign in to comment.