From 3facdba2b7a07cebde590bdbce976b376d0f8e76 Mon Sep 17 00:00:00 2001 From: Justin Coyne Date: Wed, 6 May 2015 09:58:21 -0500 Subject: [PATCH] Add direct containers 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 --- lib/active_fedora.rb | 8 +- lib/active_fedora/associations.rb | 24 +++ .../associations/builder/directly_contains.rb | 23 +++ .../associations/container_proxy.rb | 9 ++ .../associations/contains_association.rb | 2 +- .../directly_contains_association.rb | 60 +++++++ lib/active_fedora/autosave_association.rb | 2 +- lib/active_fedora/base.rb | 1 + lib/active_fedora/containers/container.rb | 30 ++++ .../containers/direct_container.rb | 7 + lib/active_fedora/core.rb | 52 +----- lib/active_fedora/file.rb | 21 +++ lib/active_fedora/file_relation.rb | 7 + lib/active_fedora/identifiable.rb | 58 +++++++ lib/active_fedora/reflection.rb | 6 +- lib/active_fedora/relation/finder_methods.rb | 8 +- .../collection_association_spec.rb | 8 +- .../complex_rdf_datastream_spec.rb | 6 +- spec/integration/direct_container_spec.rb | 151 ++++++++++++++++++ spec/unit/files_hash_spec.rb | 8 +- 20 files changed, 421 insertions(+), 70 deletions(-) create mode 100644 lib/active_fedora/associations/builder/directly_contains.rb create mode 100644 lib/active_fedora/associations/container_proxy.rb create mode 100644 lib/active_fedora/associations/directly_contains_association.rb create mode 100644 lib/active_fedora/containers/container.rb create mode 100644 lib/active_fedora/containers/direct_container.rb create mode 100644 lib/active_fedora/file_relation.rb create mode 100644 lib/active_fedora/identifiable.rb create mode 100644 spec/integration/direct_container_spec.rb diff --git a/lib/active_fedora.rb b/lib/active_fedora.rb index 78d7239f5..eb377be6a 100644 --- a/lib/active_fedora.rb +++ b/lib/active_fedora.rb @@ -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 @@ -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 diff --git a/lib/active_fedora/associations.rb b/lib/active_fedora/associations.rb index 33cb5d65c..e9223fb17 100644 --- a/lib/active_fedora/associations.rb +++ b/lib/active_fedora/associations.rb @@ -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' @@ -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' @@ -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 diff --git a/lib/active_fedora/associations/builder/directly_contains.rb b/lib/active_fedora/associations/builder/directly_contains.rb new file mode 100644 index 000000000..954530518 --- /dev/null +++ b/lib/active_fedora/associations/builder/directly_contains.rb @@ -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 + diff --git a/lib/active_fedora/associations/container_proxy.rb b/lib/active_fedora/associations/container_proxy.rb new file mode 100644 index 000000000..ff9528051 --- /dev/null +++ b/lib/active_fedora/associations/container_proxy.rb @@ -0,0 +1,9 @@ +module ActiveFedora + module Associations + class ContainerProxy < CollectionProxy + def initialize(association) + @association = association + end + end + end +end diff --git a/lib/active_fedora/associations/contains_association.rb b/lib/active_fedora/associations/contains_association.rb index 42802151d..9152fdb6d 100644 --- a/lib/active_fedora/associations/contains_association.rb +++ b/lib/active_fedora/associations/contains_association.rb @@ -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 diff --git a/lib/active_fedora/associations/directly_contains_association.rb b/lib/active_fedora/associations/directly_contains_association.rb new file mode 100644 index 000000000..1db44ffb1 --- /dev/null +++ b/lib/active_fedora/associations/directly_contains_association.rb @@ -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 diff --git a/lib/active_fedora/autosave_association.rb b/lib/active_fedora/autosave_association.rb index f1a7abf2a..9da039e46 100644 --- a/lib/active_fedora/autosave_association.rb +++ b/lib/active_fedora/autosave_association.rb @@ -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) diff --git a/lib/active_fedora/base.rb b/lib/active_fedora/base.rb index d312497c4..f08608173 100644 --- a/lib/active_fedora/base.rb +++ b/lib/active_fedora/base.rb @@ -30,6 +30,7 @@ class Base extend LdpCache::ClassMethods include Core + include Identifiable include Persistence include Indexing include Scoping diff --git a/lib/active_fedora/containers/container.rb b/lib/active_fedora/containers/container.rb new file mode 100644 index 000000000..3d4704415 --- /dev/null +++ b/lib/active_fedora/containers/container.rb @@ -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 + + diff --git a/lib/active_fedora/containers/direct_container.rb b/lib/active_fedora/containers/direct_container.rb new file mode 100644 index 000000000..2a65b76d7 --- /dev/null +++ b/lib/active_fedora/containers/direct_container.rb @@ -0,0 +1,7 @@ +module ActiveFedora + class DirectContainer < Container + type ::RDF::Vocab::LDP.DirectContainer + + + end +end diff --git a/lib/active_fedora/core.rb b/lib/active_fedora/core.rb index ecddd12ba..c29b523fc 100644 --- a/lib/active_fedora/core.rb +++ b/lib/active_fedora/core.rb @@ -1,7 +1,11 @@ module ActiveFedora module Core + extend ActiveSupport::Autoload extend ActiveSupport::Concern + autoload :FedoraIdTranslator + autoload :FedoraUriTranslator + included do ## # :singleton-method: @@ -9,30 +13,6 @@ module Core # 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 @@ -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 diff --git a/lib/active_fedora/file.rb b/lib/active_fedora/file.rb index 5ac0deb67..f1518a91e 100644 --- a/lib/active_fedora/file.rb +++ b/lib/active_fedora/file.rb @@ -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 @@ -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 @@ -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 @@ -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) @@ -253,6 +270,10 @@ def save(*) changed_attributes.clear end + def save!(*attrs) + save(*attrs) + end + def retrieve_content ldp_source.get.body end diff --git a/lib/active_fedora/file_relation.rb b/lib/active_fedora/file_relation.rb new file mode 100644 index 000000000..0a0bf6f8c --- /dev/null +++ b/lib/active_fedora/file_relation.rb @@ -0,0 +1,7 @@ +module ActiveFedora + class FileRelation < Relation + def load_from_fedora(id, _) + klass.new(klass.id_to_uri(id)) + end + end +end diff --git a/lib/active_fedora/identifiable.rb b/lib/active_fedora/identifiable.rb new file mode 100644 index 000000000..b02df1253 --- /dev/null +++ b/lib/active_fedora/identifiable.rb @@ -0,0 +1,58 @@ +module ActiveFedora + module Identifiable + extend ActiveSupport::Concern + + included do + ## + # :singleton-method + # + # Accepts a proc that takes an id and transforms it to a URI + mattr_reader :translate_id_to_uri do + Core::FedoraIdTranslator + end + + def self.translate_id_to_uri=(translator) + @@translate_id_to_uri = translator || Core::FedoraIdTranslator + end + + ## + # :singleton-method + # + # Accepts a proc that takes a uri and transforms it to an id + mattr_reader :translate_uri_to_id do + Core::FedoraUriTranslator + end + + def self.translate_uri_to_id=(translator) + @@translate_uri_to_id = translator || Core::FedoraUriTranslator + end + end + + + module ClassMethods + ## + # 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 + end + end +end diff --git a/lib/active_fedora/reflection.rb b/lib/active_fedora/reflection.rb index 58660421c..bc2e4ed6d 100644 --- a/lib/active_fedora/reflection.rb +++ b/lib/active_fedora/reflection.rb @@ -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 @@ -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 @@ -258,6 +258,8 @@ def association_class Associations::SingularRDF when :rdf Associations::RDF + when :directly_contains + Associations::DirectlyContainsAssociation end end diff --git a/lib/active_fedora/relation/finder_methods.rb b/lib/active_fedora/relation/finder_methods.rb index d7f1b5e02..4f1694568 100644 --- a/lib/active_fedora/relation/finder_methods.rb +++ b/lib/active_fedora/relation/finder_methods.rb @@ -28,7 +28,7 @@ def last end end - # Returns an Array of objects of the Class that +find+ is being + # Returns an Array of objects of the Class that +find+ is being # called on # # @param[String,Hash] args either an id or a hash of conditions @@ -40,12 +40,12 @@ def find(*args) options = options.dup cast = if @klass == ActiveFedora::Base && !options.has_key?(:cast) true - else + else options.delete(:cast) end if options[:sort] # Deprecate sort sometime? - sort = options.delete(:sort) + sort = options.delete(:sort) options[:order] ||= sort if sort.present? end @@ -251,7 +251,7 @@ def find_some(ids, cast) ids.map{|id| find_one(id, cast)} end - private + private # Returns a solr query for the supplied conditions # @param[Hash] conditions solr conditions to match diff --git a/spec/integration/collection_association_spec.rb b/spec/integration/collection_association_spec.rb index f89a788aa..d62ebcbbe 100644 --- a/spec/integration/collection_association_spec.rb +++ b/spec/integration/collection_association_spec.rb @@ -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 diff --git a/spec/integration/complex_rdf_datastream_spec.rb b/spec/integration/complex_rdf_datastream_spec.rb index fcca47007..c59cee9ff 100644 --- a/spec/integration/complex_rdf_datastream_spec.rb +++ b/spec/integration/complex_rdf_datastream_spec.rb @@ -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 @@ -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 } diff --git a/spec/integration/direct_container_spec.rb b/spec/integration/direct_container_spec.rb new file mode 100644 index 000000000..50bd3ba7a --- /dev/null +++ b/spec/integration/direct_container_spec.rb @@ -0,0 +1,151 @@ +require 'spec_helper' + +describe "Direct containers" do + describe "#directly_contains" do + context "when the class is ActiveFedora::File" do + before do + class FooHistory < ActiveFedora::Base + directly_contains :files, has_member_relation: ::RDF::URI.new("http://example.com/hasFiles") + end + end + after do + Object.send(:remove_const, :FooHistory) + end + + let(:file) { o.files.build } + let(:reloaded) { FooHistory.find(o.id) } + + context "with no files" do + let(:o) { FooHistory.new } + subject { o.files } + + it { is_expected.to be_empty } + it { is_expected.to eq [] } + end + + context "when the object exists" do + let(:o) { FooHistory.create } + + before do + file.content = "HMMM" + o.save + end + + describe "#first" do + subject { reloaded.files.first } + it "has the content" do + expect(subject.content).to eq 'HMMM' + end + end + + describe "#to_a" do + subject { reloaded.files } + it "has the content" do + expect(subject.to_a).to eq [file] + end + end + + describe "#append" do + let(:file2) { o.files.build } + it "has two files" do + expect(o.files).to eq [file, file2] + end + + context "and then saved/reloaded" do + before do + file2.content = "Derp" + o.save! + end + it "has two files" do + expect(reloaded.files).to eq [file, file2] + end + end + end + end + + context "when the object is new" do + let(:o) { FooHistory.new } + let(:file) { o.files.build } + + it "fails" do + # This is the expected behavior right now. In the future make the uri get assigned by autosave. + expect { o.files.build }.to raise_error "Can't get uri. Owner isn't saved" + end + end + end + + context "when the class is a subclass of ActiveFedora::File" do + before do + class SubFile < ActiveFedora::File; end + class FooHistory < ActiveFedora::Base + directly_contains :files, has_member_relation: ::RDF::URI.new("http://example.com/hasFiles"), class_name: 'SubFile' + end + end + after do + Object.send(:remove_const, :FooHistory) + Object.send(:remove_const, :SubFile) + end + + let(:o) { FooHistory.create } + let(:file) { o.files.build } + let(:reloaded) { FooHistory.find(o.id) } + + describe "#build" do + subject { file } + it { is_expected.to be_kind_of SubFile } + end + + context "when the object exists" do + before do + file.content = "HMMM" + o.save + end + + describe "#first" do + subject { reloaded.files.first } + it "has the content" do + expect(subject.content).to eq 'HMMM' + end + end + end + end + + context "when using is_member_of_relation" do + before do + class FooHistory < ActiveFedora::Base + directly_contains :files, is_member_of_relation: ::RDF::URI.new("http://example.com/isWithin") + end + end + after do + Object.send(:remove_const, :FooHistory) + end + + let(:file) { o.files.build } + let(:reloaded) { FooHistory.find(o.id) } + + context "with no files" do + let(:o) { FooHistory.new } + subject { o.files } + + it { is_expected.to be_empty } + it { is_expected.to eq [] } + end + + context "when the object exists" do + let(:o) { FooHistory.create } + + before do + file.content = "HMMM" + o.save + end + + describe "#first" do + subject { reloaded.files.first } + it "has the content" do + expect(subject.content).to eq 'HMMM' + end + end + end + end + end +end diff --git a/spec/unit/files_hash_spec.rb b/spec/unit/files_hash_spec.rb index 3148d71f2..2481a3fd7 100644 --- a/spec/unit/files_hash_spec.rb +++ b/spec/unit/files_hash_spec.rb @@ -2,18 +2,18 @@ describe ActiveFedora::FilesHash do before do - class Container; end - allow(Container).to receive(:child_resource_reflections).and_return(file: reflection) + class FilesContainer; end + allow(FilesContainer).to receive(:child_resource_reflections).and_return(file: reflection) allow(container).to receive(:association).with(:file).and_return(association) allow(container).to receive(:undeclared_files).and_return([]) end - after { Object.send(:remove_const, :Container) } + after { Object.send(:remove_const, :FilesContainer) } let(:reflection) { double('reflection') } let(:association) { double('association', reader: object) } let(:object) { double('object') } - let(:container) { Container.new } + let(:container) { FilesContainer.new } subject { ActiveFedora::FilesHash.new(container) }