Skip to content

Commit

Permalink
Add a Basic Container association
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed May 9, 2016
1 parent 971e609 commit fa31bf7
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 22 deletions.
19 changes: 19 additions & 0 deletions lib/active_fedora/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module Associations
autoload :HasManyAssociation
autoload :BelongsToAssociation
autoload :HasAndBelongsToManyAssociation
autoload :BasicContainsAssociation
autoload :HasSubresourceAssociation
autoload :DirectlyContainsAssociation
autoload :DirectlyContainsOneAssociation
Expand All @@ -45,6 +46,7 @@ module Builder
autoload :BelongsTo, 'active_fedora/associations/builder/belongs_to'
autoload :HasMany, 'active_fedora/associations/builder/has_many'
autoload :HasAndBelongsToMany, 'active_fedora/associations/builder/has_and_belongs_to_many'
autoload :BasicContains, 'active_fedora/associations/builder/basic_contains'
autoload :HasSubresource, 'active_fedora/associations/builder/has_subresource'
autoload :DirectlyContains, 'active_fedora/associations/builder/directly_contains'
autoload :DirectlyContainsOne, 'active_fedora/associations/builder/directly_contains_one'
Expand Down Expand Up @@ -104,6 +106,23 @@ def association_instance_set(name, association)
end

module ClassMethods
# This method is used to declare this resource acts like an LDP BasicContainer
#
# @param [Hash] options
# @option options [String] :class_name ('ActiveFedora::File') The name of the
# class that will represent the contained resources
#
# example:
# class FooHistory < ActiveFedora::Base
# is_a_container class_name: 'Thing'
# end
#
def is_a_container(options = {})
defaults = { class_name: 'ActiveFedora::File',
predicate: ::RDF::Vocab::LDP.contains }
Builder::BasicContains.build(self, :contains, defaults.merge(options))
end

# 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
#
Expand Down
22 changes: 22 additions & 0 deletions lib/active_fedora/associations/basic_contains_association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module ActiveFedora
module Associations
class BasicContainsAssociation < ContainsAssociation #:nodoc:
def find_target
uris = owner.resource.query(predicate: options[:predicate])
.map { |r| r.object.to_s }

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

def insert_record(record, force = true, validate = true)
record.base_path_for_resource = owner.uri.to_s
super
end

def add_to_target(record, skip_callbacks = false)
record.base_path_for_resource = owner.uri.to_s
super
end
end
end
end
7 changes: 7 additions & 0 deletions lib/active_fedora/associations/builder/basic_contains.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module ActiveFedora::Associations::Builder
class BasicContains < CollectionAssociation #:nodoc:
def self.macro
:is_a_container
end
end
end
20 changes: 19 additions & 1 deletion lib/active_fedora/associations/contains_association.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
# This is the parent class of DirectlyContainsAssociation and IndirectlyContainsAssociation
# This is the parent class of BasicContainsAssociation, DirectlyContainsAssociation and IndirectlyContainsAssociation
module ActiveFedora
module Associations
class ContainsAssociation < CollectionAssociation #:nodoc:
def insert_record(record, force = true, validate = true)
if force
record.save!
else
record.save(validate: validate)
end
end

def reader
@records ||= ContainerProxy.new(self)
end
Expand All @@ -27,6 +35,16 @@ def uri
raise "Can't get uri. Owner isn't saved" if @owner.new_record?
"#{@owner.uri}/#{@reflection.name}"
end

private

def delete_records(records, method)
if method == :destroy
records.each(&:destroy)
else
records.each(&:delete)
end
end
end
end
end
16 changes: 1 addition & 15 deletions lib/active_fedora/associations/directly_contains_association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ module Associations
class DirectlyContainsAssociation < ContainsAssociation #:nodoc:
def insert_record(record, force = true, validate = true)
container.save!
if force
record.save!
else
record.save(validate: validate)
end
super
end

def find_target
Expand Down Expand Up @@ -39,16 +35,6 @@ def initialize_attributes(record) #:nodoc:
record.uri = ActiveFedora::Base.id_to_uri(container.mint_id)
set_inverse_instance(record)
end

private

def delete_records(records, method)
if method == :destroy
records.each(&:destroy)
else
records.each(&:delete)
end
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 = [:has_many, :belongs_to, :has_and_belongs_to_many, :directly_contains, :indirectly_contains].freeze
ASSOCIATION_TYPES = [:has_many, :belongs_to, :has_and_belongs_to_many, :directly_contains, :indirectly_contains, :is_a_container].freeze

module AssociationBuilderExtension #:nodoc:
def self.valid_options
Expand Down
1 change: 0 additions & 1 deletion lib/active_fedora/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ class Base
include Versionable
include LoadableFromJson
include Schema
include Pathing
include Aggregation::BaseExtension
end

Expand Down
1 change: 1 addition & 0 deletions lib/active_fedora/common.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module ActiveFedora
module Common
extend ActiveSupport::Concern
include Pathing

module ClassMethods
def initialize_generated_modules # :nodoc:
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fedora/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def build_ldp_resource(id)
end

def build_ldp_resource_via_uri(uri = nil, content = '')
Ldp::Resource::BinarySource.new(ldp_connection, uri, content, ActiveFedora.fedora.host + ActiveFedora.fedora.base_path)
Ldp::Resource::BinarySource.new(ldp_connection, uri, content, base_path_for_resource)
end

def uploaded_file?(payload)
Expand Down
4 changes: 4 additions & 0 deletions lib/active_fedora/file/attributes.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
module ActiveFedora::File::Attributes
attr_writer :mime_type

def assign_attributes(_)
# nop
end

def mime_type
@mime_type ||= fetch_mime_type
end
Expand Down
10 changes: 9 additions & 1 deletion lib/active_fedora/file_persistence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module FilePersistence

def _create_record(_options = {})
return false if content.nil?
ldp_source.content = content
@ldp_source = build_ldp_binary_source
ldp_source.create do |req|
req.headers.merge!(ldp_headers)
end
Expand All @@ -23,5 +23,13 @@ def _update_record(_options = {})
end
refresh
end

def build_ldp_binary_source
if id
build_ldp_resource_via_uri(uri, content)
else
build_ldp_resource_via_uri(nil, content)
end
end
end
end
8 changes: 7 additions & 1 deletion lib/active_fedora/ldp_resource_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ def build(klass, id)
if id
LdpResource.new(connection, to_uri(klass, id))
else
LdpResource.new(connection, nil, nil, ActiveFedora.fedora.host + ActiveFedora.fedora.base_path)
parent_uri = ActiveFedora.fedora.host + ActiveFedora.fedora.base_path
LdpResource.new(connection, nil, nil, parent_uri)
end
end

def build_resource_under_path(graph, parent_uri)
parent_uri ||= ActiveFedora.fedora.host + ActiveFedora.fedora.base_path
LdpResource.new(connection, nil, graph, parent_uri)
end

def update(change_set, klass, id)
SparqlInsert.new(change_set.changes).execute(to_uri(klass, id))
end
Expand Down
11 changes: 10 additions & 1 deletion lib/active_fedora/persistence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ def eradicate
self.class.eradicate(id)
end

# Used when setting containment
def base_path_for_resource=(path)
@base_path = path
end

module ClassMethods
# Creates an object (or multiple objects) and saves it to the repository, if validations pass.
# The resulting object is returned whether the object was saved successfully to the repository or not.
Expand Down Expand Up @@ -201,11 +206,15 @@ def assign_rdf_subject
@ldp_source = if !id && new_id = assign_id
LdpResource.new(ActiveFedora.fedora.connection, self.class.id_to_uri(new_id), @resource)
else
LdpResource.new(ActiveFedora.fedora.connection, @ldp_source.subject, @resource, ActiveFedora.fedora.host + base_path_for_resource)
LdpResource.new(ActiveFedora.fedora.connection, @ldp_source.subject, @resource, base_path_for_resource)
end
end

def base_path_for_resource
@base_path ||= ActiveFedora.fedora.host + default_base_path_for_resource
end

def default_base_path_for_resource
init_root_path if has_uri_prefix?
root_resource_path
end
Expand Down
16 changes: 16 additions & 0 deletions lib/active_fedora/reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def create(macro, name, scope, options, active_fedora)
DirectlyContainsOneReflection
when :indirectly_contains
IndirectlyContainsReflection
when :is_a_container
BasicContainsReflection
when :rdf
RDFPropertyReflection
when :singular_rdf
Expand Down Expand Up @@ -513,6 +515,20 @@ def association_class
end
end

class BasicContainsReflection < AssociationReflection # :nodoc:
def macro
:is_a_container
end

def collection?
true
end

def association_class
Associations::BasicContainsAssociation
end
end

class DirectlyContainsReflection < AssociationReflection # :nodoc:
def macro
:directly_contains
Expand Down
108 changes: 108 additions & 0 deletions spec/integration/basic_contains_association_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
require 'spec_helper'

describe ActiveFedora::Base do
let(:model) { Source.new }

context "with a file" do
before do
class Source < ActiveFedora::Base
is_a_container
end
end

after do
Object.send(:remove_const, :Source)
end

it 'is empty' do
expect(model.contains).to eq []
end

it 'can build a child' do
child = model.contains.build
expect(child).to be_kind_of ActiveFedora::File
child.content = "hello"
model.save!
expect(child).to be_persisted
expect(child.uri.to_s).to include model.uri.to_s
end

it 'can create a child on a persisted parent' do
model.save!
child = model.contains.build
expect(child).to be_kind_of ActiveFedora::File
child.content = "hello"
model.save!
expect(child).to be_persisted
expect(child.uri.to_s).to include model.uri.to_s
end
end

context "with an AF::Base object" do
before do
class Thing < ActiveFedora::Base
property :title, predicate: ::RDF::Vocab::DC.title
end
class Source < ActiveFedora::Base
is_a_container class_name: 'Thing'
end
end
after do
Object.send(:remove_const, :Source)
Object.send(:remove_const, :Thing)
end

let(:model) { Source.new }

it 'is empty' do
expect(model.contains).to eq []
end

describe "creating" do
it 'can build a child' do
child = model.contains.build
expect(model.contains.build(title: ['my title'])).to be_kind_of Thing
model.save!
expect(child).to be_persisted
expect(child.uri.to_s).to include model.uri.to_s
end

it 'can create a child on a persisted parent' do
model.save!
child = model.contains.create(title: ['my title'])
expect(child).to be_kind_of Thing
expect(child).to be_persisted
expect(child.uri.to_s).to include model.uri.to_s
end
end

describe "loading" do
before do
model.save!
model.contains.create(title: ['title 1'])
model.contains.create(title: ['title 2'])
model.reload
end

it "has the two contained objects" do
expect(model.contains.size).to eq 2
expect(model.contains.map(&:title)).to eq [['title 1'], ['title 2']]
end
end

describe "#destroy_all" do
before do
model.save!
model.contains.create(title: ['title 1'])
model.contains.create(title: ['title 2'])
model.reload
end

it "destroys the two contained objects" do
expect { model.contains.destroy_all }
.to change { model.contains.size }.by(-2)
.and change { Thing.count }.by(-2)
end
end
end
end

0 comments on commit fa31bf7

Please sign in to comment.