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, has_member_relation:
     ::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 7, 2015
1 parent 2c6f867 commit 3facdba
Show file tree
Hide file tree
Showing 20 changed files with 421 additions and 70 deletions.
8 changes: 5 additions & 3 deletions lib/active_fedora.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ module ActiveFedora #:nodoc:
autoload :CleanConnection
autoload :Config
autoload :Core
autoload_under 'core' do
autoload :FedoraIdTranslator
autoload :FedoraUriTranslator
autoload_under 'containers' do
autoload :Container
autoload :DirectContainer
end
autoload :Datastream
autoload :Datastreams
Expand All @@ -64,8 +64,10 @@ module ActiveFedora #:nodoc:
autoload :File
autoload :FileConfigurator
autoload :FilePathBuilder
autoload :FileRelation
autoload :FilesHash
autoload :FixityService
autoload :Identifiable
autoload :Indexing
autoload :IndexingService
autoload :InheritableAccessors
Expand Down
24 changes: 24 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,27 @@ def association_instance_set(name, association)

module ClassMethods

# This method is used to declare an ldp:DirectContainer 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
#
# example:
# class FooHistory < ActiveFedora::Base
# directly_contains :files, has_member_relation:
# ::RDF::URI.new("http://example.com/hasFiles"), class_name: 'Thing'
# directly_contains :other_stuff, is_member_of_relation:
# ::RDF::URI.new("http://example.com/isContainedBy"), 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
23 changes: 23 additions & 0 deletions lib/active_fedora/associations/builder/directly_contains.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module ActiveFedora::Associations::Builder
class DirectlyContains < CollectionAssociation #:nodoc:
self.macro = :directly_contains
self.valid_options += [:has_member_relation, :is_member_of_relation]
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
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
60 changes: 60 additions & 0 deletions lib/active_fedora/associations/directly_contains_association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module ActiveFedora
module Associations
class DirectlyContainsAssociation < CollectionAssociation #:nodoc:

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

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

def find_target
query_node = if container_predicate = options[:has_member_relation]
owner
else
container_predicate = ::RDF::Vocab::LDP.contains
container
end

uris = query_node.resource.query(predicate: container_predicate).map { |r| r.object.to_s }

uris.map { |object_uri| klass.find(klass.uri_to_id(object_uri)) }
end

def container
@container ||= begin
DirectContainer.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])
end
end
end

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
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
1 change: 1 addition & 0 deletions lib/active_fedora/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Base
extend LdpCache::ClassMethods

include Core
include Identifiable
include Persistence
include Indexing
include Scoping
Expand Down
30 changes: 30 additions & 0 deletions lib/active_fedora/containers/container.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module ActiveFedora
# This is the base class for ldp containers, it is not an ldp:BasicContainer
class Container < ActiveFedora::Base

property :membership_resource, predicate: ::RDF::Vocab::LDP.membershipResource
property :has_member_relation, predicate: ::RDF::Vocab::LDP.hasMemberRelation
property :is_member_of_relation, predicate: ::RDF::Vocab::LDP.isMemberOfRelation

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
52 changes: 4 additions & 48 deletions lib/active_fedora/core.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,18 @@
module ActiveFedora
module Core
extend ActiveSupport::Autoload
extend ActiveSupport::Concern

autoload :FedoraIdTranslator
autoload :FedoraUriTranslator

included do
##
# :singleton-method:
#
# Accepts a logger conforming to the interface of Log4r which can be
# retrieved on both a class and instance level by calling +logger+.
mattr_accessor :logger, instance_writer: false

##
# :singleton-method
#
# Accepts a proc that takes an id and transforms it to a URI
mattr_reader :translate_id_to_uri do
FedoraIdTranslator
end

def self.translate_id_to_uri=(translator)
@@translate_id_to_uri = translator || FedoraIdTranslator
end

##
# :singleton-method
#
# Accepts a proc that takes a uri and transforms it to an id
mattr_reader :translate_uri_to_id do
FedoraUriTranslator
end

def self.translate_uri_to_id=(translator)
@@translate_uri_to_id = translator || FedoraUriTranslator
end
end

def ldp_source
Expand Down Expand Up @@ -141,30 +121,6 @@ def to_class_uri(attrs = {})
name
end

##
# Transforms an id into a uri
# if translate_id_to_uri is set it uses that proc, otherwise just the default
def id_to_uri(id)
translate_id_to_uri.call(id)
end

##
# Transforms a uri into an id
# if translate_uri_to_id is set it uses that proc, otherwise just the default
def uri_to_id(uri)
translate_uri_to_id.call(uri)
end

##
# Provides the common interface for ActiveTriples::Identifiable
def from_uri(uri,_)
begin
self.find(uri_to_id(uri))
rescue ActiveFedora::ObjectNotFoundError, Ldp::Gone
ActiveTriples::Resource.new(uri)
end
end

private

def relation
Expand Down
21 changes: 21 additions & 0 deletions lib/active_fedora/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class File
generate_method 'content'

extend ActiveModel::Callbacks
include Identifiable
include Scoping
extend Querying
define_model_callbacks :save, :create, :destroy
define_model_callbacks :initialize, only: :after

Expand Down Expand Up @@ -42,6 +45,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 +76,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 @@ -217,6 +231,9 @@ def links
@links ||= Ldp::Response.links(ldp_source.head)
end

def self.relation
FileRelation.new(self)
end

# Rack::Test::UploadedFile is often set via content=, however it's not an IO, though it wraps an io object.
def behaves_like_io?(obj)
Expand Down Expand Up @@ -253,6 +270,10 @@ def save(*)
changed_attributes.clear
end

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

def retrieve_content
ldp_source.get.body
end
Expand Down
7 changes: 7 additions & 0 deletions lib/active_fedora/file_relation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module ActiveFedora
class FileRelation < Relation
def load_from_fedora(id, _)
klass.new(klass.id_to_uri(id))
end
end
end
Loading

0 comments on commit 3facdba

Please sign in to comment.