├── .gitmodules ├── .rspec ├── lib ├── active-fedora.rb ├── generators │ └── active_fedora │ │ ├── config │ │ ├── solr │ │ │ ├── templates │ │ │ │ ├── solr │ │ │ │ │ └── conf │ │ │ │ │ │ ├── spellings.txt │ │ │ │ │ │ ├── _rest_managed.json │ │ │ │ │ │ ├── protwords.txt │ │ │ │ │ │ ├── scripts.conf │ │ │ │ │ │ ├── admin-extra.html │ │ │ │ │ │ ├── synonyms.txt │ │ │ │ │ │ ├── elevate.xml │ │ │ │ │ │ ├── stopwords.txt │ │ │ │ │ │ └── stopwords_en.txt │ │ │ │ ├── solr_wrapper_test.yml │ │ │ │ ├── .solr_wrapper.yml │ │ │ │ └── solr.yml │ │ │ └── solr_generator.rb │ │ ├── fedora │ │ │ ├── templates │ │ │ │ ├── fcrepo_wrapper_test.yml │ │ │ │ ├── .fcrepo_wrapper.yml │ │ │ │ └── fedora.yml │ │ │ └── fedora_generator.rb │ │ ├── USAGE │ │ └── config_generator.rb │ │ └── model │ │ ├── USAGE │ │ ├── templates │ │ ├── datastream_spec.rb.erb │ │ ├── datastream.rb.erb │ │ ├── model_spec.rb.erb │ │ └── model.rb.erb │ │ └── model_generator.rb └── active_fedora │ ├── version.rb │ ├── containers │ ├── direct_container.rb │ ├── indirect_container.rb │ └── container.rb │ ├── locale │ └── en.yml │ ├── null_relation.rb │ ├── type.rb │ ├── file_relation.rb │ ├── associations │ ├── builder │ │ ├── basic_contains.rb │ │ ├── singular_property.rb │ │ ├── property.rb │ │ ├── filter.rb │ │ ├── has_and_belongs_to_many.rb │ │ ├── directly_contains.rb │ │ ├── has_many.rb │ │ ├── singular_association.rb │ │ ├── belongs_to.rb │ │ ├── indirectly_contains.rb │ │ ├── has_subresource.rb │ │ └── aggregation.rb │ ├── null_validator.rb │ ├── singular_rdf.rb │ ├── container_proxy.rb │ ├── delete_proxy.rb │ ├── id_composite.rb │ ├── record_composite.rb │ ├── basic_contains_association.rb │ ├── directly_contains_association.rb │ ├── association_scope.rb │ ├── contains_association.rb │ ├── belongs_to_association.rb │ └── singular_association.rb │ ├── indexers.rb │ ├── runtime_registry.rb │ ├── attributes │ ├── primary_key.rb │ ├── node_config.rb │ ├── active_triple_attribute.rb │ └── serializers.rb │ ├── aggregation.rb │ ├── indexers │ ├── null_indexer.rb │ └── global_indexer.rb │ ├── orders.rb │ ├── rspec_matchers.rb │ ├── property.rb │ ├── orders │ ├── collection_proxy.rb │ └── target_proxy.rb │ ├── checksum.rb │ ├── core │ ├── fedora_uri_translator.rb │ └── fedora_id_translator.rb │ ├── rdf.rb │ ├── config.rb │ ├── rdf │ ├── value_caster.rb │ ├── project_hydra.rb │ ├── persistence.rb │ └── field_map_entry.rb │ ├── delegated_attribute.rb │ ├── files_hash.rb │ ├── persistence │ └── null_identifier_service.rb │ ├── with_metadata │ ├── sweet_jpl_terms.rb │ ├── default_strategy.rb │ ├── default_metadata_class_factory.rb │ └── default_schema.rb │ ├── association_relation.rb │ ├── attribute_methods │ ├── dirty.rb │ └── write.rb │ ├── relation │ ├── calculations.rb │ ├── merger.rb │ ├── delegation.rb │ └── spawn_methods.rb │ ├── pathing.rb │ ├── serialization.rb │ ├── null_logger.rb │ ├── type │ └── boolean.rb │ ├── aggregation │ ├── base_extension.rb │ ├── ordered_reader.rb │ └── proxy.rb │ ├── querying.rb │ ├── query_result_builder.rb │ ├── test_support.rb │ ├── inbound_relation_connection.rb │ ├── schema_indexing_strategy.rb │ ├── inheritable_accessors.rb │ ├── default_model_mapper.rb │ ├── file_persistence.rb │ ├── file │ └── external.rb │ ├── railtie.rb │ ├── schema.rb │ ├── versions_graph.rb │ ├── file_path_builder.rb │ ├── inheritance.rb │ ├── ldp_resource.rb │ ├── with_metadata.rb │ ├── rspec_matchers │ ├── belong_to_associated_active_fedora_object_matcher.rb │ ├── have_predicate_matcher.rb │ └── have_many_associated_active_fedora_objects_matcher.rb │ ├── clean_connection.rb │ ├── ldp_cache.rb │ ├── log_subscriber.rb │ ├── ldp_resource_service.rb │ ├── indexing │ ├── map.rb │ ├── descriptor.rb │ └── inserter.rb │ ├── cleaner.rb │ ├── solr_hit.rb │ ├── initializing_connection.rb │ ├── base.rb │ ├── versionable.rb │ ├── identifiable.rb │ └── attribute_assignment.rb ├── spec ├── rcov.opts ├── spec.opts ├── fixtures │ ├── dino.jpg │ ├── minivan.jpg │ ├── dino_jpg_no_file_ext │ └── rails_root │ │ └── config │ │ ├── fake_fedora.yml │ │ ├── solr_mappings_af_0.1.yml │ │ ├── fedora.yml │ │ ├── solr_mappings.yml │ │ ├── solr_mappings_bl_2.4.yml │ │ └── solr.yml ├── lib │ └── active_fedora │ │ └── null_logger_spec.rb ├── unit │ ├── builder │ │ └── has_and_belongs_to_many_spec.rb │ ├── base_cma_spec.rb │ ├── logger_spec.rb │ ├── ldp_resource_spec.rb │ ├── core │ │ ├── logger_spec.rb │ │ ├── fedora_uri_translator_spec.rb │ │ └── fedora_id_translator_spec.rb │ ├── file_path_builder_spec.rb │ ├── querying_spec.rb │ ├── serializers_spec.rb │ ├── config_spec.rb │ ├── readonly_spec.rb │ ├── orders │ │ └── reflection_spec.rb │ ├── indexing │ │ ├── map │ │ │ └── index_object_spec.rb │ │ └── map_spec.rb │ ├── property_spec.rb │ ├── solr_query_builder_spec.rb │ ├── collection_proxy_spec.rb │ ├── checksum_spec.rb │ ├── pathing_spec.rb │ ├── indexing_service_spec.rb │ ├── base_active_model_spec.rb │ ├── rdf_vocab_spec.rb │ ├── fedora_spec.rb │ ├── base_extra_spec.rb │ ├── file │ │ └── streaming_spec.rb │ ├── loadable_from_json_spec.rb │ ├── property_predicate_spec.rb │ ├── code_configurator_spec.rb │ ├── sparql_insert_spec.rb │ ├── default_model_mapper_spec.rb │ ├── indexers │ │ └── global_indexer_spec.rb │ ├── inheritance_spec.rb │ ├── forbidden_attributes_protection_spec.rb │ ├── rspec_matchers │ │ ├── have_predicate_matcher_spec.rb │ │ ├── belong_to_associated_active_fedora_object_matcher_spec.rb │ │ └── have_many_associated_active_fedora_objects_matcher_spec.rb │ ├── reflection_spec.rb │ ├── query_result_builder_spec.rb │ ├── aggregation │ │ └── ordered_reader_spec.rb │ ├── active_fedora │ │ └── indexing │ │ │ └── inserter_spec.rb │ ├── files_hash_spec.rb │ ├── scoping_spec.rb │ ├── solr_hit_spec.rb │ └── with_metadata │ │ └── default_metadata_class_factory_spec.rb ├── config_helper.rb ├── integration │ ├── generators │ │ ├── solr_generator_spec.rb │ │ └── fedora_generator_spec.rb │ ├── autosave_association_spec.rb │ ├── clean_connection_spec.rb │ ├── solr_hit_spec.rb │ ├── json_serialization_spec.rb │ ├── gone_spec.rb │ ├── file_fixity_spec.rb │ ├── fedora_solr_sync_spec.rb │ ├── marshal_spec.rb │ ├── has_subresource_spec.rb │ ├── persistence_spec.rb │ ├── nested_hash_resources_spec.rb │ ├── query_result_builder_spec.rb │ ├── eradicate_spec.rb │ ├── relation_delegation_spec.rb │ └── scoping_spec.rb └── support │ └── an_active_model.rb ├── .github_changelog_generator ├── config ├── fcrepo_allow.txt ├── service_mappings.yml ├── solr.yml └── fedora.yml ├── .solr_wrapper ├── .gitignore ├── script ├── console ├── destroy └── generate ├── .fcrepo_wrapper ├── Rakefile ├── CONTRIBUTORS.md ├── SUPPORT.md ├── .rubocop_todo.yml ├── LICENSE ├── Gemfile └── .mailmap /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | -------------------------------------------------------------------------------- /lib/active-fedora.rb: -------------------------------------------------------------------------------- 1 | require 'active_fedora' 2 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude "spec/*,gems/*" 2 | --rails 3 | -------------------------------------------------------------------------------- /.github_changelog_generator: -------------------------------------------------------------------------------- 1 | unreleased=true 2 | future-release=13.1.2 3 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --format progress 3 | --loadby mtime 4 | --reverse 5 | 6 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/spellings.txt: -------------------------------------------------------------------------------- 1 | pizza 2 | history 3 | -------------------------------------------------------------------------------- /config/fcrepo_allow.txt: -------------------------------------------------------------------------------- 1 | https://example.com/ 2 | http://example.com/ 3 | http://localhost:8986/ 4 | -------------------------------------------------------------------------------- /spec/fixtures/dino.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samvera/active_fedora/HEAD/spec/fixtures/dino.jpg -------------------------------------------------------------------------------- /spec/fixtures/minivan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samvera/active_fedora/HEAD/spec/fixtures/minivan.jpg -------------------------------------------------------------------------------- /lib/active_fedora/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActiveFedora 4 | VERSION = '16.0.0' 5 | end 6 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/_rest_managed.json: -------------------------------------------------------------------------------- 1 | { 2 | "initArgs":{}, 3 | "managedList":[]} -------------------------------------------------------------------------------- /spec/fixtures/dino_jpg_no_file_ext: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samvera/active_fedora/HEAD/spec/fixtures/dino_jpg_no_file_ext -------------------------------------------------------------------------------- /lib/active_fedora/containers/direct_container.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class DirectContainer < Container 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/active_fedora/locale/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activefedora: 3 | errors: 4 | messages: 5 | record_invalid: "Validation failed: %{errors}" 6 | -------------------------------------------------------------------------------- /lib/active_fedora/null_relation.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module NullRelation # :nodoc: 3 | def exists?(_id = false) 4 | false 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/active_fedora/type.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'active_fedora/type/value' 3 | require 'active_fedora/type/boolean' 4 | 5 | module ActiveFedora 6 | module Type 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/active_fedora/file_relation.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class FileRelation < Relation 3 | def load_from_fedora(id, _) 4 | klass.new(klass.id_to_uri(id)) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/fedora/templates/fcrepo_wrapper_test.yml: -------------------------------------------------------------------------------- 1 | #config/fcrepo_wrapper_test.yml.sample 2 | port: 8986 3 | enable_jms: false 4 | fcrepo_home_dir: tmp/fcrepo4-test-data 5 | -------------------------------------------------------------------------------- /spec/fixtures/rails_root/config/fake_fedora.yml: -------------------------------------------------------------------------------- 1 | development: 2 | url: http://development.com/fedora 3 | test: 4 | url: http://test.com/fedora 5 | production: 6 | url: http://production.com/fedora 7 | 8 | -------------------------------------------------------------------------------- /.solr_wrapper: -------------------------------------------------------------------------------- 1 | # Place any default configuration for solr_wrapper here 2 | version: 7.7.3 3 | port: 8985 4 | collection: 5 | dir: lib/generators/active_fedora/config/solr/templates/solr/conf/ 6 | name: hydra-test 7 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/fedora/templates/.fcrepo_wrapper.yml: -------------------------------------------------------------------------------- 1 | # Place any default configuration for fcrepo_wrapper here 2 | port: 8984 3 | enable_jms: false 4 | fcrepo_home_dir: tmp/fcrepo4-development-data 5 | -------------------------------------------------------------------------------- /lib/active_fedora/containers/indirect_container.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class IndirectContainer < Container 3 | property :inserted_content_relation, predicate: ::RDF::Vocab::LDP.insertedContentRelation 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/basic_contains.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class BasicContains < CollectionAssociation # :nodoc: 3 | def self.macro 4 | :is_a_container 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Generate ActiveFedora config files 3 | 4 | Example: 5 | rails generate active_fedora:config 6 | 7 | This will create: 8 | config/fedora.yml 9 | config/solr.yml 10 | -------------------------------------------------------------------------------- /lib/active_fedora/indexers.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Indexers 3 | extend ActiveSupport::Autoload 4 | 5 | eager_autoload do 6 | autoload :NullIndexer 7 | autoload :GlobalIndexer 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/active_fedora/runtime_registry.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/module/attribute_accessors_per_thread' 2 | 3 | module ActiveFedora 4 | class RuntimeRegistry 5 | thread_mattr_accessor :solr_service, :fedora_connection 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/rails_root/config/solr_mappings_af_0.1.yml: -------------------------------------------------------------------------------- 1 | id: id 2 | default: searchable 3 | searchable: 4 | default: _field 5 | date: _date 6 | displayable: _display 7 | facetable: _facet 8 | sortable: _sort 9 | unstemmed_searchable: _unstem_search -------------------------------------------------------------------------------- /lib/active_fedora/associations/null_validator.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations 2 | ## 3 | # An association type validator which does no validation. 4 | class NullValidator 5 | def self.validate!(_reflection, _object); end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr_wrapper_test.yml: -------------------------------------------------------------------------------- 1 | #config/solr_wrapper_test.yml 2 | # version: 6.1.0 3 | port: 8985 4 | instance_dir: tmp/solr-test 5 | collection: 6 | persist: false 7 | dir: solr/conf 8 | name: hydra-test 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | rdoc 3 | pkg 4 | /.bundle 5 | .rvmrc 6 | .yardoc 7 | doc 8 | tmp 9 | Gemfile.lock 10 | jetty 11 | bin 12 | .idea 13 | .ruby-version 14 | .ruby-gemset 15 | gemfiles/*.gemfile.lock 16 | /fcrepo4-data 17 | /fcrepo4-test-data 18 | /spec/examples.txt 19 | -------------------------------------------------------------------------------- /lib/active_fedora/attributes/primary_key.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Attributes 3 | module PrimaryKey 4 | # If the id is "/foo:1" then to_key ought to return ["foo:1"] 5 | def to_key 6 | id && [id] 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/.solr_wrapper.yml: -------------------------------------------------------------------------------- 1 | # Place any default configuration for solr_wrapper here 2 | # version: 6.0.0 3 | # port: 8983 4 | instance_dir: tmp/solr-development 5 | collection: 6 | persist: true 7 | dir: solr/conf/ 8 | name: hydra-development 9 | -------------------------------------------------------------------------------- /lib/active_fedora/aggregation.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Aggregation 3 | extend ActiveSupport::Autoload 4 | eager_autoload do 5 | autoload :Proxy 6 | autoload :BaseExtension 7 | autoload :OrderedReader 8 | autoload :ListSource 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/singular_property.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class SingularProperty < Property 3 | def self.macro 4 | :singular_rdf 5 | end 6 | 7 | def self.better_name(name) 8 | :"#{name}_id" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/active_fedora/indexers/null_indexer.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Indexers 2 | ## 3 | # An indexer which does nothing with the given index object. 4 | class NullIndexer 5 | include Singleton 6 | def new(_) 7 | self 8 | end 9 | 10 | def index(_); end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/active_fedora/orders.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Orders 3 | extend ActiveSupport::Autoload 4 | 5 | eager_autoload do 6 | autoload :CollectionProxy 7 | autoload :ListNode 8 | autoload :OrderedList 9 | autoload :TargetProxy 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/active_fedora/rspec_matchers.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | # in ./spec/spec_helper.rb 3 | # ``` require 'active_fedora/rspec_matchers' ``` 4 | module RspecMatchers 5 | end 6 | end 7 | pattern = Dir.glob(File.join(File.dirname(__FILE__), 'rspec_matchers/*_matcher.rb')) 8 | pattern.each { |f| require f } 9 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/singular_rdf.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Associations 3 | class SingularRDF < RDF # :nodoc: 4 | def replace(value) 5 | super(Array(value)) 6 | end 7 | 8 | def reader 9 | super.first 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /config/service_mappings.yml: -------------------------------------------------------------------------------- 1 | :service_mapping: 2 | "fedora-system:3": 3 | :object_profile: viewObjectProfile 4 | :method_index: viewMethodIndex 5 | :item_index: viewItemIndex 6 | :dc_view: viewDublinCore 7 | "test:12": 8 | :document_style_1: getDocumentStyle1 9 | :document_style_2: getDocumentStyle2 10 | -------------------------------------------------------------------------------- /lib/active_fedora/property.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class Property 3 | attr_accessor :name, :instance_variable_name 4 | 5 | def initialize(_model, name, _type, _options = {}) 6 | @name = name 7 | @instance_variable_name = "@#{@name}" 8 | end 9 | 10 | def field; end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/active_fedora/orders/collection_proxy.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Orders 3 | class CollectionProxy < ActiveFedora::Associations::CollectionProxy 4 | attr_reader :association 5 | delegate :append_target, :insert_target_at, :insert_target_id_at, :delete_at, to: :association 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/config_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | 3 | module ActiveFedora 4 | class ConfigGenerator < Rails::Generators::Base 5 | def generate_configs 6 | generate('active_fedora:config:fedora') 7 | generate('active_fedora:config:solr') 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/fixtures/rails_root/config/fedora.yml: -------------------------------------------------------------------------------- 1 | test: 2 | user: fedoraAdmin 3 | password: fedoraAdmin 4 | url: http://testhost.com:8983/fedora 5 | test_ssl: 6 | user: fedoraAdmin 7 | password: fedoraAdmin 8 | url: https://testhost.com:8443/fedora 9 | ssl: 10 | verify: false 11 | ca_path: /path/to/certs 12 | -------------------------------------------------------------------------------- /lib/active_fedora/attributes/node_config.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Attributes 2 | class NodeConfig < ActiveTriples::NodeConfig 3 | def multiple? 4 | @multiple 5 | end 6 | 7 | def initialize(term, predicate, options = {}) 8 | super 9 | @multiple = options[:multiple] 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/active_fedora/checksum.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class Checksum 3 | attr_reader :uri, :value, :algorithm 4 | 5 | def initialize(file) 6 | @uri = file.digest.first 7 | return unless @uri 8 | @algorithm, @value = @uri.path.split(":") 9 | @algorithm.upcase! 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/active_fedora/core/fedora_uri_translator.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Core 2 | class FedoraUriTranslator 3 | SLASH = '/'.freeze 4 | def self.call(uri) 5 | id = uri.to_s.sub(ActiveFedora.fedora.host + ActiveFedora.fedora.base_path, '') 6 | id.start_with?(SLASH) ? id[1..-1] : id 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/active_fedora/rdf.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module RDF 3 | extend ActiveSupport::Autoload 4 | autoload :Fcrepo 5 | autoload :IndexingService 6 | autoload :Persistence 7 | autoload :ProjectHydra 8 | autoload :FieldMap 9 | autoload :FieldMapEntry 10 | autoload :ValueCaster 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/active_fedora/config.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class Config 3 | attr_reader :credentials 4 | def initialize(val) 5 | @credentials = val.deep_symbolize_keys 6 | return if @credentials.key?(:url) 7 | raise ActiveFedora::ConfigurationError, "Fedora configuration must provide :url." 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/active_fedora/rdf/value_caster.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::RDF 2 | class ValueCaster 3 | def initialize(value) 4 | @value = value 5 | end 6 | 7 | def value 8 | if @value.respond_to?(:language) # Cast RDF::Literals 9 | @value.to_s 10 | else 11 | @value 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/container_proxy.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Associations 3 | class ContainerProxy < CollectionProxy 4 | # rubocop:disable Lint/MissingSuper 5 | def initialize(association) 6 | @association = association 7 | end 8 | # rubocop:enable Lint/MissingSuper 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/rails_root/config/solr_mappings.yml: -------------------------------------------------------------------------------- 1 | id: id 2 | default: searchable 3 | searchable: 4 | date: _dt 5 | string: _t 6 | text: _t 7 | symbol: _s 8 | integer: _i 9 | long: _l 10 | boolean: _b 11 | float: _f 12 | double: _d 13 | displayable: _display 14 | facetable: _facet 15 | sortable: _sort 16 | unstemmed_searchable: _unstem_search -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/property.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class Property < Association 3 | def self.macro 4 | :rdf 5 | end 6 | 7 | def self.valid_options(options) 8 | super 9 | end 10 | 11 | def self.better_name(name) 12 | :"#{name.to_s.singularize}_ids" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/active_fedora/delegated_attribute.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | # Represents the mapping between a model attribute and a property 3 | class DelegatedAttribute 4 | attr_accessor :field, :multiple 5 | 6 | def initialize(field, args = {}) 7 | self.field = field 8 | self.multiple = args.fetch(:multiple, false) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/active_fedora/files_hash.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class FilesHash < AssociationHash 3 | def initialize(model, reflections = nil) 4 | super 5 | end 6 | 7 | def reflections 8 | @base.class.child_resource_reflections 9 | end 10 | 11 | def keys 12 | reflections.keys + @base.undeclared_files 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/lib/active_fedora/null_logger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::NullLogger do 4 | [:debug?, :info?, :warn?, :error?, :fatal?].each do |method_name| 5 | describe "##{method_name}" do 6 | subject { described_class.new.public_send(method_name) } 7 | it { is_expected.to be_falsey } 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/fixtures/rails_root/config/solr_mappings_bl_2.4.yml: -------------------------------------------------------------------------------- 1 | id: id 2 | default: searchable 3 | searchable: 4 | id: id 5 | date: _dt 6 | string: _t 7 | text: _t 8 | symbol: _s 9 | integer: _i 10 | long: _l 11 | boolean: _b 12 | float: _f 13 | double: _d 14 | displayable: _display 15 | facetable: _facet 16 | sortable: _sort 17 | unstemmed_searchable: _unstem_search -------------------------------------------------------------------------------- /lib/active_fedora/persistence/null_identifier_service.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Persistence 3 | # An identifier service that doesn't mint IDs, so that the autocreated 4 | # identifiers from Fedora will be used. 5 | class NullIdentifierService 6 | # Effectively a no-op 7 | # @return [NilClass] 8 | def mint; end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/model/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Generate a class that inherits from ActiveFedora::Base 3 | 4 | Example: 5 | rails generate active_fedora:model Journal 6 | 7 | This will create: 8 | app/models/journal.rb 9 | app/models/datastreams/journal_metadata.rb 10 | spec/models/journal_spec.rb 11 | spec/models/datastreams/journal_metadata_spec.rb 12 | -------------------------------------------------------------------------------- /script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # File: script/console 3 | irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' 4 | 5 | requires = %w{ irb/completion samples/samples } 6 | includes = %w{ lib spec } 7 | 8 | def to_param(flag, items) 9 | items.map{|lib| "#{flag} #{lib}"}.join(' ') 10 | end 11 | 12 | exec "#{irb} #{to_param('-I', includes)} #{to_param('-r', requires)} --simple-prompt" 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/active_fedora/with_metadata/sweet_jpl_terms.rb: -------------------------------------------------------------------------------- 1 | require 'rdf' 2 | module ActiveFedora::WithMetadata 3 | class SweetJPLTerms < RDF::StrictVocabulary('http://sweet.jpl.nasa.gov/2.2/reprDataFormat.owl#') 4 | # Property definitions 5 | property :byteOrder, 6 | comment: ['Byte Order.'.freeze], 7 | range: 'xsd:string'.freeze, 8 | label: 'Byte Order'.freeze 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/unit/builder/has_and_belongs_to_many_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Associations::Builder::HasAndBelongsToMany do 4 | describe "valid_options" do 5 | subject { described_class.valid_options({}) } 6 | it { is_expected.to match_array [:class_name, :predicate, :type_validator, :before_add, :after_add, :before_remove, :after_remove, :inverse_of, :solr_page_size, :autosave] } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/unit/base_cma_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | before do 5 | @test_object = described_class.new 6 | end 7 | 8 | describe '.save' do 9 | it "adds hasModel relationship that points to the CModel if @new_object" do 10 | allow(@test_object).to receive(:update_index) 11 | expect(@test_object).to receive(:refresh) 12 | @test_object.save 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/active_fedora/association_relation.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class AssociationRelation < Relation 3 | def initialize(klass, association) 4 | super(klass) 5 | @association = association 6 | end 7 | 8 | def proxy_association 9 | @association 10 | end 11 | 12 | private 13 | 14 | def exec_queries 15 | super.each { |r| @association.set_inverse_instance r } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /script/destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 3 | 4 | begin 5 | require 'rubigen' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'rubigen' 9 | end 10 | require 'rubigen/scripts/destroy' 11 | 12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0]) 13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] 14 | RubiGen::Scripts::Destroy.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 3 | 4 | begin 5 | require 'rubigen' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'rubigen' 9 | end 10 | require 'rubigen/scripts/generate' 11 | 12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0]) 13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] 14 | RubiGen::Scripts::Generate.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/model/templates/datastream_spec.rb.erb: -------------------------------------------------------------------------------- 1 | # Generated via 2 | # `rails generate active_fedora:model <%= class_name %>` 3 | require 'rails_helper' 4 | 5 | describe <%= class_name %>Metadata do 6 | it 'should have a title' do 7 | pending "Create a terminology for <%= class_name %>Metadata, then enable this test" 8 | subject.title = 'War and Peace' 9 | expect(subject.title).to eq ['War and Peace'] 10 | end 11 | 12 | end 13 | 14 | -------------------------------------------------------------------------------- /spec/fixtures/rails_root/config/solr.yml: -------------------------------------------------------------------------------- 1 | development: 2 | default: 3 | url: http://localhost:8983/solr/development 4 | full_text: 5 | url: http://localhost:8983/solr/development 6 | test: 7 | default: 8 | url: http://localhost:8983/solr/test 9 | full_text: 10 | url: http://localhost:8983/solr/test 11 | production: 12 | default: 13 | url: http://localhost:8080/solr/production 14 | full_text: 15 | url: http://localhost:8080/solr/production -------------------------------------------------------------------------------- /lib/active_fedora/attribute_methods/dirty.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module AttributeMethods 3 | module Dirty 4 | extend ActiveSupport::Concern 5 | 6 | def set_value(*val) 7 | attribute = val.first 8 | unless [:has_model, :modified_date].include? attribute 9 | attribute_will_change!(attribute) unless Array(self[val.first]).to_set == Array(val.last).to_set 10 | end 11 | super 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/active_fedora/core/fedora_id_translator.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Core 2 | class FedoraIdTranslator 3 | SLASH = '/'.freeze 4 | def self.call(id) 5 | id = URI::DEFAULT_PARSER.escape(id, '[]'.freeze) 6 | id = "/#{id}" unless id.start_with? SLASH 7 | id = ActiveFedora.fedora.base_path + id unless ActiveFedora.fedora.base_path == SLASH || id.start_with?("#{ActiveFedora.fedora.base_path}/") 8 | ActiveFedora.fedora.host + id 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr.yml: -------------------------------------------------------------------------------- 1 | # This is a sample config file that points to a solr server for each environment 2 | development: 3 | url: <%= ENV['SOLR_URL'] || "http://127.0.0.1:#{ENV.fetch('SOLR_DEVELOPMENT_PORT', 8983)}/solr/hydra-development" %> 4 | test: 5 | url: <%= ENV['SOLR_URL'] || "http://127.0.0.1:#{ENV.fetch('SOLR_TEST_PORT', 8985)}/solr/hydra-test" %> 6 | production: 7 | url: <%= ENV['SOLR_URL'] || "http://your.production.server:8080/bl_solr/core0" %> 8 | -------------------------------------------------------------------------------- /lib/active_fedora/relation/calculations.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Calculations 3 | # Get a count of the number of objects from solr 4 | # Takes :conditions as an argument 5 | def count(*args) 6 | return apply_finder_options(args.first).count if args.any? 7 | opts = {} 8 | opts[:rows] = limit_value if limit_value 9 | opts[:sort] = order_values if order_values 10 | 11 | SolrService.count(create_query(where_values)) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/unit/logger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | let(:logger1) { instance_double(::Logger, debug?: false) } 5 | 6 | before do 7 | @initial_logger = described_class.logger 8 | described_class.logger = logger1 9 | end 10 | 11 | after do 12 | described_class.logger = @initial_logger 13 | end 14 | 15 | it "Allows loggers to be set" do 16 | expect(logger1).to receive(:warn).with("Hey") 17 | described_class.new.logger.warn "Hey" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.fcrepo_wrapper: -------------------------------------------------------------------------------- 1 | # Place any default configuration for solr_wrapper here 2 | port: 8986 3 | version: 6.4.0 4 | enable_jms: false 5 | java_options: ['-Dfcrepo.log.http.api=DEBUG','-Dfcrepo.log.kernel=ERROR','-Xmx512m', 6 | '-Dfcrepo.external.content.allowed=config/fcrepo_allow.txt', '-Dfcrepo.autoversioning.enabled=false'] 7 | #managed: true 8 | #fcrepo_home_dir: tmp/fcrepo-development-data 9 | url: https://github.com/fcrepo/fcrepo/releases/download/fcrepo-6.4.0/fcrepo-webapp-6.4.0-jetty-console.jar 10 | ignore_md5sum: true 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | require 'bundler' 3 | 4 | Bundler::GemHelper.install_tasks 5 | 6 | # load rake tasks defined in lib/tasks that are not loaded in lib/active_fedora.rb 7 | load "lib/tasks/active_fedora_dev.rake" 8 | 9 | CLEAN.include %w(**/.DS_Store tmp *.log *.orig *.tmp **/*~) 10 | 11 | desc 'setup jetty and run tests' 12 | task ci: ['active_fedora:ci'] 13 | desc 'run tests' 14 | task spec: ['active_fedora:rubocop', 'active_fedora:rspec'] 15 | task rcov: ['active_fedora:rcov'] 16 | 17 | task default: [:ci] 18 | -------------------------------------------------------------------------------- /lib/active_fedora/pathing.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Pathing 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | def uri_prefix 7 | nil 8 | end 9 | 10 | def has_uri_prefix? 11 | !uri_prefix.nil? 12 | end 13 | 14 | def root_resource_path 15 | if has_uri_prefix? 16 | ActiveFedora.fedora.base_path + "/" + uri_prefix 17 | else 18 | ActiveFedora.fedora.base_path 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/active_fedora/serialization.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora # :nodoc: 2 | # = Active Fedora Serialization 3 | module Serialization 4 | extend ActiveSupport::Concern 5 | include ActiveModel::Serializers::JSON 6 | 7 | included do 8 | self.include_root_in_json = false 9 | end 10 | 11 | def serializable_hash(options = nil) 12 | options = options.try(:clone) || {} 13 | 14 | options[:except] = Array(options[:except]).map(&:to_s) 15 | 16 | super(options) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/fedora/fedora_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | 3 | module ActiveFedora 4 | class Config::FedoraGenerator < Rails::Generators::Base 5 | source_root ::File.expand_path('../templates', __FILE__) 6 | 7 | def generate 8 | copy_file('fedora.yml', 'config/fedora.yml') 9 | end 10 | 11 | def fcrepo_wrapper_config 12 | copy_file '.fcrepo_wrapper.yml' 13 | copy_file 'fcrepo_wrapper_test.yml', 'config/fcrepo_wrapper_test.yml' 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/ldp_resource_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::LdpResource do 4 | let(:obj) { ActiveFedora::Base.create! } 5 | let!(:r1) { described_class.new(ActiveFedora.fedora.connection, obj.uri) } 6 | let!(:r2) { described_class.new(ActiveFedora.fedora.connection, obj.uri) } 7 | 8 | it "caches requests" do 9 | expect_any_instance_of(Faraday::Connection).to receive(:get).once.and_call_original 10 | ActiveFedora::Base.cache do 11 | r1.get 12 | r2.get 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/active_fedora/attributes/active_triple_attribute.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | # Attributes delegated to ActiveTriples. Allows ActiveFedora to track all attributes consistently. 3 | # 4 | # @example 5 | # class Book < ActiveFedora::Base 6 | # property :title, predicate: ::RDF::Vocab::DC.title 7 | # property :author, predicate: ::RDF::Vocab::DC.creator 8 | # end 9 | # 10 | # Book.attribute_names 11 | # => ["title", "author"] 12 | 13 | class ActiveTripleAttribute < DelegatedAttribute 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/active_fedora/null_logger.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class NullLogger < Logger 3 | # rubocop:disable Lint/MissingSuper 4 | def initialize(*); end 5 | # rubocop:enable Lint/MissingSuper 6 | 7 | # allows all the usual logger method calls (warn, info, error, etc.) 8 | def add(*); end 9 | 10 | # In the NullLogger there are no levels, so none of these should be true. 11 | [:debug?, :info?, :warn?, :error?, :fatal?].each do |method_name| 12 | define_method(method_name) { false } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/filter.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class Filter < ActiveFedora::Associations::Builder::CollectionAssociation 3 | def self.valid_options(options) 4 | super + [:extending_from, :condition] 5 | end 6 | 7 | def self.macro 8 | :filter 9 | end 10 | 11 | def self.define_readers(mixin, name) 12 | super 13 | mixin.redefine_method("#{name.to_s.singularize}_ids") do 14 | association(name).ids_reader 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config/solr.yml: -------------------------------------------------------------------------------- 1 | development: 2 | default: 3 | url: http://127.0.0.1:<%= ENV['SOLR_TEST_PORT'] || 8983 %>/solr/hydra-development 4 | full_text: 5 | url: http://localhost:8983/solr/development 6 | test: 7 | default: 8 | url: http://localhost:<%= ENV['SOLR_TEST_PORT'] || 8985 %>/solr/hydra-test 9 | full_text: 10 | url: http://localhost:<%= ENV['SOLR_TEST_PORT'] || 8985 %>/solr/test 11 | production: 12 | default: 13 | url: http://localhost:8080/solr/production 14 | full_text: 15 | url: http://localhost:8080/solr/production 16 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Contributors to this project: 2 | 3 | * Adam Wead 4 | * Andrew Curley 5 | * Benjamin Armintor 6 | * Bess Sadler 7 | * Carolyn Cole 8 | * Chris Beer 9 | * Chris Colvard 10 | * David Chandek-Stark 11 | * Edwin Shin 12 | * Jeremy Friesen 13 | * Jessie Keck 14 | * John Scofield 15 | * Justin Coyne 16 | * Matt Zumwalt 17 | * Michael B. Klein 18 | * Michael J. Giarlo 19 | * Michael Klein 20 | * Mirosław Boruta 21 | * Naomi Dushay 22 | * Richard Johnson 23 | * Thomas Johnson 24 | * mpc3c 25 | * Steven Anderson 26 | * Trey Terrell 27 | 28 | -------------------------------------------------------------------------------- /spec/unit/core/logger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::NullLogger do 4 | before { ActiveFedora::Base.logger = described_class.new } 5 | describe "::logger" do 6 | let(:logger) { ActiveFedora::Base.logger } 7 | it "when calling the logger" do 8 | expect(logger.warn("warning!")).to be_nil 9 | end 10 | end 11 | 12 | describe "#logger" do 13 | let(:logger) { ActiveFedora::Base.new.logger } 14 | it "when calling the logger" do 15 | expect(logger.warn("warning!")).to be_nil 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/unit/file_path_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::FilePathBuilder do 4 | describe ".build" do 5 | subject { described_class.build(parent, nil, 'FOO') } 6 | let(:parent) { ActiveFedora::Base.new(id: '1234') } 7 | 8 | it { is_expected.to eq 'FOO1' } 9 | 10 | context "when some datastreams exist" do 11 | before do 12 | allow(parent).to receive(:attached_files).and_return('FOO56' => instance_double(ActiveFedora::File)) 13 | end 14 | 15 | it { is_expected.to eq 'FOO57' } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/active_fedora/type/boolean.rb: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT THIS FILE 2 | # Plucked from ActiveModel::Type::Boolean in Rails 5 3 | module ActiveFedora 4 | module Type 5 | class Boolean < Value # :nodoc: 6 | FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set 7 | 8 | def type 9 | :boolean 10 | end 11 | 12 | private 13 | 14 | def cast_value(value) 15 | if value == '' 16 | nil 17 | else 18 | !FALSE_VALUES.include?(value) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/active_fedora/aggregation/base_extension.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Aggregation 2 | module BaseExtension 3 | def ordered_by 4 | ordered_by_ids.lazy.map { |x| ActiveFedora::Base.find(x) } 5 | end 6 | 7 | private 8 | 9 | def ordered_by_ids 10 | if id.present? 11 | query = "{!join from=proxy_in_ssi to=id}ordered_targets_ssim:#{id}" 12 | rows = ActiveFedora::SolrService::MAX_ROWS 13 | ActiveFedora::SolrService.query(query, rows: rows).map { |x| x["id"] } 14 | else 15 | [] 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/querying_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Querying do 4 | before do 5 | class SpecModel < ActiveFedora::Base 6 | end 7 | end 8 | 9 | after do 10 | Object.send(:remove_const, :SpecModel) 11 | end 12 | 13 | describe '.solr_query_handler' do 14 | subject { SpecModel.solr_query_handler } 15 | 16 | it { is_expected.to eq 'standard' } 17 | 18 | context "when setting to something besides the default" do 19 | before { SpecModel.solr_query_handler = 'search' } 20 | 21 | it { is_expected.to eq 'search' } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/active_fedora/querying.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Querying 3 | delegate :find, :first, :exists?, :where, :limit, :offset, :order, :delete_all, 4 | :destroy_all, :count, :last, :search_with_conditions, :search_in_batches, :search_by_id, :find_each, to: :all 5 | 6 | def self.extended(base) 7 | base.class_attribute :solr_query_handler 8 | base.solr_query_handler = 'standard' 9 | end 10 | 11 | def default_sort_params 12 | [ActiveFedora.index_field_mapper.solr_name('system_create', :stored_sortable, type: :date) + 13 | ' asc'] 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/active_fedora/relation/merger.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class Relation 3 | class HashMerger # :nodoc: 4 | end 5 | 6 | class Merger # :nodoc: 7 | attr_reader :relation, :values, :other 8 | 9 | def initialize(relation, other) 10 | @relation = relation 11 | @values = other.values 12 | @other = other 13 | end 14 | 15 | def merge 16 | # TODO: merge order 17 | # See https://github.com/samvera/active_fedora/issues/1329 18 | relation.where_values += other.where_values 19 | relation 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/serializers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Attributes::Serializers do 4 | describe "deserialize_dates_from_form" do 5 | before do 6 | class Foo < ActiveFedora::Base 7 | attr_accessor :birthday 8 | end 9 | end 10 | after do 11 | Object.send(:remove_const, :Foo) 12 | end 13 | subject(:serializer) { Foo.new } 14 | it "deserializes dates" do 15 | serializer.attributes = { 'birthday(1i)' => '2012', 'birthday(2i)' => '10', 'birthday(3i)' => '31' } 16 | expect(serializer.birthday).to eq '2012-10-31' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/active_fedora/query_result_builder.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module QueryResultBuilder 3 | def self.lazy_reify_solr_results(solr_results, opts = {}) 4 | return to_enum(:lazy_reify_solr_results, solr_results, opts) unless block_given? 5 | 6 | solr_results.each do |hit| 7 | yield ActiveFedora::SolrHit.for(hit).reify(opts) 8 | end 9 | end 10 | 11 | def self.reify_solr_results(solr_results, opts = {}) 12 | lazy_reify_solr_results(solr_results, opts).to_a 13 | end 14 | 15 | HAS_MODEL_SOLR_FIELD = ActiveFedora.index_field_mapper.solr_name("has_model", :symbol).freeze 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/active_fedora/rdf/project_hydra.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require 'rdf' 3 | class ActiveFedora::RDF::ProjectHydra < ::RDF::StrictVocabulary("http://projecthydra.org/ns/relations#") 4 | property :hasProfile, 5 | label: "Has Profile".freeze, 6 | subPropertyOf: "info:fedora/fedora-system:def/relations-external#fedoraRelationship".freeze, 7 | type: "rdf:Property".freeze 8 | property :isGovernedBy, 9 | label: "Is Governed By".freeze, 10 | subPropertyOf: "info:fedora/fedora-system:def/relations-external#fedoraRelationship".freeze, 11 | type: "rdf:Property".freeze 12 | end 13 | -------------------------------------------------------------------------------- /spec/unit/core/fedora_uri_translator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Core::FedoraUriTranslator do 4 | describe ".call" do 5 | let(:result) { described_class.call(uri) } 6 | context "when given a Fedora URI" do 7 | let(:uri) { ActiveFedora.fedora.base_uri + "/6" } 8 | it "returns the id" do 9 | expect(result).to eq '6' 10 | end 11 | end 12 | context "when given a URI missing a slash" do 13 | let(:uri) { ActiveFedora.fedora.base_uri + "602-a" } 14 | it "returns the id" do 15 | expect(result).to eq "602-a" 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/has_and_belongs_to_many.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class HasAndBelongsToMany < CollectionAssociation # :nodoc: 3 | def self.macro 4 | :has_and_belongs_to_many 5 | end 6 | 7 | def self.valid_options(options) 8 | super + [:inverse_of, :solr_page_size] 9 | end 10 | 11 | def self.validate_options(options) 12 | super 13 | raise ArgumentError, "You must specify a predicate for #{name}" unless options[:predicate] 14 | raise ArgumentError, "Predicate must be a kind of RDF::URI" unless options[:predicate].is_a?(RDF::URI) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/config_helper.rb: -------------------------------------------------------------------------------- 1 | def mock_yaml(hash, path) 2 | mock_file = instance_double(File, path.split("/")[-1]) 3 | allow(File).to receive(:exist?).with(path).and_return(true) 4 | allow(File).to receive(:open).with(path).and_return(mock_file) 5 | allow(Psych).to receive(:load).and_return(hash) 6 | end 7 | 8 | def stub_rails(opts = {}) 9 | Object.const_set("Rails", Class) 10 | Rails.send(:undef_method, :env) if Rails.respond_to?(:env) 11 | Rails.send(:undef_method, :root) if Rails.respond_to?(:root) 12 | opts.each { |k, v| Rails.send(:define_method, k) { return v } } 13 | end 14 | 15 | def unstub_rails 16 | Object.send(:remove_const, :Rails) if defined?(Rails) 17 | end 18 | -------------------------------------------------------------------------------- /lib/active_fedora/with_metadata/default_strategy.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::WithMetadata 2 | class DefaultStrategy < ActiveTriples::ExtensionStrategy 3 | # override apply method to check if property already exists or receiver already has predicate defined. 4 | # Do not add property if the rdf_resource already responds to the property name 5 | # Do not add property if the rdf_resource already has a property with the same predicate. 6 | def self.apply(resource, property) 7 | return if resource.respond_to?(property.name) 8 | return if resource.properties.any? { |p| p[1].predicate == property.predicate } 9 | resource.property property.name, property.to_h 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/active_fedora/aggregation/ordered_reader.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Aggregation 2 | ## 3 | # Lazily iterates over a doubly linked list, fixing up nodes if necessary. 4 | class OrderedReader 5 | include Enumerable 6 | attr_reader :root 7 | def initialize(root) 8 | @root = root 9 | end 10 | 11 | def each 12 | proxy = first_head 13 | while proxy 14 | yield proxy unless proxy.nil? 15 | next_proxy = proxy.next 16 | next_proxy.try(:prev=, proxy) if next_proxy && next_proxy.prev != proxy 17 | proxy = next_proxy 18 | end 19 | end 20 | 21 | private 22 | 23 | def first_head 24 | root.head 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | If you would like to report an issue, first search [the list of issues](https://github.com/samvera/active_fedora/issues/) to see if someone else has already reported it, and then feel free to [create a new issue](https://github.com/samvera/active_fedora/issues/new). 2 | 3 | If you have questions or need help, please email [the Samvera community tech list](https://groups.google.com/forum/#!forum/samvera-tech) or stop by the #dev channel in [the Samvera community Slack team](https://wiki.duraspace.org/pages/viewpage.action?pageId=87460391#Getintouch!-Slack). 4 | 5 | You can learn more about the various Samvera communication channels on the [Get in touch!](https://wiki.duraspace.org/pages/viewpage.action?pageId=87460391) wiki page. 6 | -------------------------------------------------------------------------------- /spec/unit/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Config do 4 | context "with a single fedora instance" do 5 | let(:yaml) { Psych.load(File.read('spec/fixtures/rails_root/config/fedora.yml')) } 6 | let(:section) { 'test' } 7 | let(:conf) { described_class.new(yaml[section]) } 8 | 9 | describe "#credentials" do 10 | subject { conf.credentials } 11 | it { is_expected.to eq(url: 'http://testhost.com:8983/fedora', user: 'fedoraAdmin', password: 'fedoraAdmin') } 12 | describe "with SSL options" do 13 | let(:section) { 'test_ssl' } 14 | its([:ssl]) { is_expected.to eq(verify: false, ca_path: '/path/to/certs') } 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/active_fedora/aggregation/proxy.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Aggregation 2 | class Proxy < ActiveFedora::Base 3 | belongs_to :container, predicate: ::RDF::Vocab::ORE.proxyIn, class_name: 'ActiveFedora::Base' 4 | belongs_to :target, predicate: ::RDF::Vocab::ORE.proxyFor, class_name: 'ActiveFedora::Base' 5 | belongs_to :next, predicate: ::RDF::Vocab::IANA.next, class_name: 'ActiveFedora::Aggregation::Proxy' 6 | belongs_to :prev, predicate: ::RDF::Vocab::IANA.prev, class_name: 'ActiveFedora::Aggregation::Proxy' 7 | 8 | type ::RDF::Vocab::ORE.Proxy 9 | 10 | def as_list 11 | if self.next 12 | [self] + self.next.as_list 13 | else 14 | [self] 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/solr_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | 3 | module ActiveFedora 4 | class Config::SolrGenerator < Rails::Generators::Base 5 | source_root ::File.expand_path('../templates', __FILE__) 6 | 7 | def generate 8 | # Overwrite the configuration files that Blacklight has installed 9 | copy_file 'solr.yml', 'config/solr.yml', force: true 10 | directory 'solr', 'solr', force: true 11 | end 12 | 13 | def solr_wrapper_config 14 | # Overwrite the configuration files that Blacklight has installed 15 | copy_file '.solr_wrapper.yml', force: true 16 | copy_file 'solr_wrapper_test.yml', 'config/solr_wrapper_test.yml' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/readonly_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | subject(:object) { described_class.new } 5 | it { is_expected.not_to be_readonly } 6 | 7 | describe "#readonly!" do 8 | it "makes the object readonly" do 9 | expect { object.readonly! }.to change { object.readonly? }.from(false).to(true) 10 | end 11 | end 12 | 13 | context "a readonly record" do 14 | before { object.readonly! } 15 | 16 | it "does not be destroyable" do 17 | expect { object.destroy }.to raise_error ActiveFedora::ReadOnlyRecord 18 | end 19 | 20 | it "does not be mutable" do 21 | expect { object.save }.to raise_error ActiveFedora::ReadOnlyRecord 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/delete_proxy.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations 2 | class DeleteProxy 3 | def self.call(proxy_ids:, proxy_class:) 4 | new(proxy_ids: proxy_ids, proxy_class: proxy_class).run 5 | end 6 | attr_reader :proxy_ids, :proxy_class 7 | 8 | def initialize(proxy_ids:, proxy_class:) 9 | @proxy_ids = proxy_ids 10 | @proxy_class = proxy_class 11 | end 12 | 13 | def run 14 | proxies.each(&:delete) 15 | end 16 | 17 | private 18 | 19 | def proxies 20 | @proxies ||= proxy_ids.map { |uri| uri_to_proxy(uri) } 21 | end 22 | 23 | def uri_to_proxy(uri) 24 | proxy_class.find(proxy_class.uri_to_id(uri)) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/active_fedora/relation/delegation.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Delegation # :nodoc: 3 | extend ActiveSupport::Concern 4 | 5 | # This module creates compiled delegation methods dynamically at runtime, which makes 6 | # subsequent calls to that method faster by avoiding method_missing. The delegations 7 | # may vary depending on the klass of a relation, so we create a subclass of Relation 8 | # for each different klass, and the delegations are compiled into that subclass only. 9 | 10 | delegate :length, :map, :to_ary, :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :shuffle, :slice, :index, :rindex, :size, to: :to_a 11 | delegate :any?, :all?, :collect, :include?, to: :each 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/integration/generators/solr_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'generators/active_fedora/config/solr/solr_generator' 3 | 4 | describe ActiveFedora::Config::SolrGenerator do 5 | describe "#solr_wrapper_config" do 6 | let(:generator) { described_class.new } 7 | let(:files_to_test) { [ 8 | 'config/solr_wrapper_test.yml', 9 | '.solr_wrapper.yml' 10 | ]} 11 | 12 | it "creates config files" do 13 | Dir.mktmpdir do |dir| 14 | Dir.chdir(dir) do 15 | generator.solr_wrapper_config 16 | files_to_test.each do |file| 17 | expect(File).to exist(file), "Expected #{file} to exist" 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/an_active_model.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "An ActiveModel" do 2 | def assert(test, *_args) 3 | expect(test).to be true 4 | end 5 | 6 | def assert_kind_of(klass, inspected_object) 7 | expect(inspected_object).to be_kind_of(klass) 8 | end 9 | 10 | def assert_equal(the_other, one, _description = nil) 11 | expect(one).to eq the_other 12 | end 13 | 14 | def assert_respond_to(obj, meth, _msg = nil) 15 | expect(obj).to respond_to meth 16 | end 17 | 18 | include ActiveModel::Lint::Tests 19 | 20 | ActiveModel::Lint::Tests.public_instance_methods.map(&:to_s).grep(/^test/).each do |m| 21 | example m.tr('_', ' ') do 22 | send m 23 | end 24 | end 25 | 26 | def model 27 | subject 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/unit/orders/reflection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Reflection::OrdersReflection do 4 | let(:orders_reflection) { described_class.new(name, scope, options, active_fedora) } 5 | let(:macro) { :orders } 6 | let(:name) { "ordered_member_proxies" } 7 | let(:options) { {} } 8 | let(:scope) { nil } 9 | let(:active_fedora) { instance_double(ActiveFedora::Base) } 10 | 11 | describe "#klass" do 12 | it "is a proxy" do 13 | expect(orders_reflection.klass).to eq ActiveFedora::Orders::ListNode 14 | end 15 | end 16 | 17 | describe "#class_name" do 18 | it "is a list node" do 19 | expect(orders_reflection.class_name).to eq "ActiveFedora::Orders::ListNode" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/fedora.yml: -------------------------------------------------------------------------------- 1 | development: 2 | user: fedoraAdmin 3 | password: fedoraAdmin 4 | url: http://<%= ENV['FCREPO_HOST'] || 'localhost' %>:<%= ENV['FCREPO_DEVELOPMENT_PORT'] || ENV['FCREPO_PORT'] || 8984 %>/<%= ENV['FCREPO_REST_PATH'] || 'rest' %> 5 | base_path: <%= ENV['FCREPO_BASE_PATH'] || '/dev' %> 6 | test: 7 | user: fedoraAdmin 8 | password: fedoraAdmin 9 | url: http://<%= ENV['FCREPO_HOST'] || 'localhost' %>:<%= ENV['FCREPO_TEST_PORT'] || ENV['FCREPO_PORT'] || 8986 %>/<%= ENV['FCREPO_REST_PATH'] || 'rest' %> 10 | base_path: <%= ENV['FCREPO_BASE_PATH'] || '/test' %> 11 | production: 12 | user: fedoraAdmin 13 | password: fedoraAdmin 14 | url: http://<%= ENV['FCREPO_HOST'] || 'localhost' %>:<%= ENV['FCREPO_PORT'] || 8983 %>/<%= ENV['FCREPO_REST_PATH'] || 'rest' %> 15 | -------------------------------------------------------------------------------- /lib/active_fedora/test_support.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../spec/support/an_active_model', __FILE__) 2 | module ActiveFedora 3 | module TestSupport 4 | # Assert that the :subject's :association_name equals the input :object 5 | def assert_active_fedora_belongs_to(subject, association_name, object) 6 | subject.send(association_name).must_equal object 7 | end 8 | 9 | # Assert that the :subject's :association_name contains all of the :objects 10 | def assert_active_fedora_has_many(subject, association_name, objects) 11 | association = subject.send(association_name) 12 | assert_equal objects.count, association.count 13 | objects.each do |object| 14 | assert association.include?(object) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/integration/autosave_association_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | before :all do 5 | class MockAFBaseRelationship < ActiveFedora::Base 6 | end 7 | end 8 | after :all do 9 | Object.send(:remove_const, :MockAFBaseRelationship) 10 | end 11 | 12 | subject(:relationship) { MockAFBaseRelationship.new } 13 | 14 | context '#changed_for_autosave?' do 15 | before do 16 | expect(relationship).to receive(:new_record?).and_return(false) 17 | expect(relationship).to receive(:changed?).and_return(false) 18 | expect(relationship).to receive(:marked_for_destruction?).and_return(false) 19 | end 20 | it { 21 | expect { relationship.changed_for_autosave? }.to_not raise_error 22 | } 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/indexing/map/index_object_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Indexing::Map::IndexObject do 4 | describe "with a block" do 5 | subject(:instance) do 6 | described_class.new(:name) do |index| 7 | index.as :stored_searchable, :facetable 8 | end 9 | end 10 | 11 | it "can set behaviors" do 12 | expect(instance.behaviors).to eq [:stored_searchable, :facetable] 13 | end 14 | end 15 | 16 | describe "with an initializer parameters" do 17 | subject(:instance) do 18 | described_class.new(:name, behaviors: [:stored_searchable, :facetable]) 19 | end 20 | 21 | it "can set behaviors" do 22 | expect(instance.behaviors).to eq [:stored_searchable, :facetable] 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/active_fedora/inbound_relation_connection.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | # Use this connection judiciously, on fedora 4.3, these requests 3 | # may be 3-5x slower, and make a graph that takes longer to parse. 4 | class InboundRelationConnection < SimpleDelegator 5 | def get(*args) 6 | result = __getobj__.get(*args) do |req| 7 | prefer_headers = Ldp::PreferHeaders.new(req.headers["Prefer"]) 8 | prefer_headers.include = prefer_headers.include | include_uris 9 | req.headers["Prefer"] = prefer_headers.to_s 10 | yield req if block_given? 11 | end 12 | result 13 | end 14 | 15 | private 16 | 17 | def include_uris 18 | [ 19 | ::RDF::Vocab::Fcrepo4.InboundReferences 20 | ] 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/property_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Property do 4 | before do 5 | @test_property = described_class.new(instance_double(ActiveFedora::Base), "file_name", :string) 6 | end 7 | 8 | it 'provides .new' do 9 | expect(described_class).to respond_to(:new) 10 | end 11 | 12 | it 'provides .name' do 13 | expect(described_class).to respond_to(:name) 14 | end 15 | 16 | it 'provides .instance_variable_name' do 17 | expect(@test_property).to respond_to(:instance_variable_name) 18 | end 19 | 20 | describe '.instance_variable_name' do 21 | it 'returns the value of the name attribute with an @ appended' do 22 | expect(@test_property.instance_variable_name).to eql("@#{@test_property.name}") 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/integration/generators/fedora_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'generators/active_fedora/config/fedora/fedora_generator' 3 | 4 | describe ActiveFedora::Config::FedoraGenerator do 5 | describe "#fcrepo_wrapper_config" do 6 | let(:generator) { described_class.new } 7 | let(:files_to_test) { [ 8 | 'config/fcrepo_wrapper_test.yml', 9 | '.fcrepo_wrapper.yml' 10 | ]} 11 | 12 | before do 13 | generator.fcrepo_wrapper_config 14 | end 15 | 16 | after do 17 | files_to_test.each { |file| File.delete(file) if File.exist?(file) } 18 | end 19 | 20 | it "creates config files" do 21 | files_to_test.each do |file| 22 | expect(File).to exist(file), "Expected #{file} to exist" 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/solr_query_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::SolrQueryBuilder do 4 | describe "construct_query" do 5 | it "generates a query clause" do 6 | expect(described_class.construct_query('id' => "my:_ID1_")).to eq '_query_:"{!field f=id}my:_ID1_"' 7 | end 8 | end 9 | 10 | describe '#construct_query_for_ids' do 11 | it "generates a useable solr query from an array of Fedora ids" do 12 | expect(described_class.construct_query_for_ids(["my:_ID1_", "my:_ID2_", "my:_ID3_"])).to eq '{!terms f=id}my:_ID1_,my:_ID2_,my:_ID3_' 13 | end 14 | it "returns a valid solr query even if given an empty array as input" do 15 | expect(described_class.construct_query_for_ids([""])).to eq "id:NEVER_USE_THIS_ID" 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2018-05-16 14:10:19 -0500 using RuboCop version 0.56.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Cop supports --auto-correct. 11 | # Configuration parameters: PreferredDelimiters. 12 | Style/PercentLiteralDelimiters: 13 | Exclude: 14 | - 'Rakefile' 15 | 16 | # Offense count: 1 17 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 18 | # URISchemes: http, https 19 | Layout/LineLength: 20 | Max: 82 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Stanford University Libraries (SULAIR) and MediaShelf, LLC 2 | Copyright 2011 The Pennsylvania State University 3 | Copyright 2012 University of Notre Dame 4 | Additional copyright may be held by others, as reflected in the commit history. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/directly_contains.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class DirectlyContains < CollectionAssociation # :nodoc: 3 | def self.macro 4 | :directly_contains 5 | end 6 | 7 | def self.valid_options(options) 8 | super + [:has_member_relation, :is_member_of_relation] - [:predicate] 9 | end 10 | 11 | def self.validate_options(options) 12 | super 13 | 14 | raise ArgumentError, "You must specify a :has_member_relation or :is_member_of_relation predicate for #{name}" if !options[:has_member_relation] && !options[:is_member_of_relation] 15 | raise ArgumentError, "Predicate must be a kind of RDF::URI" if !options[:has_member_relation].is_a?(RDF::URI) && !options[:is_member_of_relation].is_a?(RDF::URI) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/integration/clean_connection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::CleanConnection do 4 | subject { ActiveFedora.fedora.clean_connection } 5 | describe "#get" do 6 | context "when given an existing resource uri" do 7 | let(:uri) { asset.rdf_subject } 8 | let(:asset) do 9 | ActiveFedora::Base.create do |a| 10 | a.resource << [a.rdf_subject, RDF::Vocab::DC.title, "test"] 11 | end 12 | end 13 | let(:result) { subject.get(uri) } 14 | it "returns a clean graph" do 15 | graph = result.graph 16 | expect(graph.statements.to_a.length).to eq 1 17 | expect(graph.statements.to_a.first).to contain_exactly asset.rdf_subject, RDF::Vocab::DC.title, RDF::Literal.new("test") 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/integration/solr_hit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::SolrHit do 4 | before do 5 | class Foo < ActiveFedora::Base 6 | property :title, predicate: ::RDF::Vocab::DC.title, multiple: false 7 | end 8 | end 9 | 10 | after do 11 | Object.send(:remove_const, :Foo) 12 | end 13 | 14 | subject(:solr_hit) { described_class.new(doc) } 15 | let(:another) { Foo.create } 16 | 17 | let!(:obj) { Foo.create!( 18 | id: 'test-123', 19 | title: 'My Title' 20 | ) } 21 | 22 | let(:doc) { obj.to_solr } 23 | 24 | describe "#reify" do 25 | let(:solr_reified) { solr_hit.reify } 26 | 27 | it "finds the document in solr" do 28 | expect(solr_reified).to be_instance_of Foo 29 | expect(solr_reified.title).to eq 'My Title' 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Bundler will rely on active-fedora.gemspec for dependency information. 4 | 5 | gemspec path: File.expand_path('..', __FILE__) 6 | 7 | gem 'byebug' unless ENV['TRAVIS'] 8 | gem 'pry-byebug' unless ENV['CI'] 9 | 10 | if ENV['RAILS_VERSION'] 11 | gem 'activemodel', ENV['RAILS_VERSION'] 12 | gem 'rails', ENV['RAILS_VERSION'] 13 | else 14 | gem 'activemodel', '>= 6.0', '< 8' 15 | gem 'rails', '>= 6.0', '< 8' 16 | end 17 | 18 | group :test do 19 | gem 'coveralls', require: false 20 | gem 'rspec_junit_formatter' 21 | gem 'simplecov', require: false 22 | end 23 | 24 | gem 'jruby-openssl', platform: :jruby 25 | 26 | # rdf-tabular has a dependency on csv but it was removed from the ruby standard library starting in 3.4 27 | gem "csv", "~> 3.0" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3') 28 | -------------------------------------------------------------------------------- /spec/unit/collection_proxy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Associations::CollectionProxy do 4 | before do 5 | class Book < ActiveFedora::Base 6 | end 7 | 8 | class Page < ActiveFedora::Base 9 | end 10 | end 11 | 12 | after do 13 | Object.send(:remove_const, :Page) 14 | Object.send(:remove_const, :Book) 15 | end 16 | 17 | describe "#spawn" do 18 | subject { proxy.spawn } 19 | 20 | let(:reflection) { ActiveFedora::Reflection.create(:has_many, :pages, nil, { predicate: ActiveFedora::RDF::Fcrepo::RelsExt.isMemberOfCollection }, Book) } 21 | let(:association) { ActiveFedora::Associations::HasManyAssociation.new(Book.new, reflection) } 22 | let(:proxy) { described_class.new(association) } 23 | 24 | it { is_expected.to be_instance_of ActiveFedora::Relation } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/active_fedora/indexers/global_indexer.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Indexers 2 | ## 3 | # Applies indexing hints to any given property, independent of what that 4 | # property 5 | class GlobalIndexer 6 | # @param [Array] index_types The indexing hints to use. 7 | def initialize(index_types = nil) 8 | @index_types = Array.wrap(index_types) 9 | end 10 | 11 | # The global indexer acts as both an indexer factory and an indexer, since 12 | # the property doesn't matter. 13 | def new(_property) 14 | self 15 | end 16 | 17 | # @param [ActiveFedora::Indexing::Map::IndexObject, #as] index_obj The indexing 18 | # object to call #as on. 19 | def index(index_obj) 20 | index_obj.as(*index_types) unless index_types.empty? 21 | end 22 | 23 | private 24 | 25 | attr_reader :index_types 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/fedora/templates/fedora.yml: -------------------------------------------------------------------------------- 1 | development: 2 | user: fedoraAdmin 3 | password: fedoraAdmin 4 | url: http://<%= ENV['FCREPO_HOST'] || 'localhost' %>:<%= ENV['FCREPO_DEVELOPMENT_PORT'] || ENV['FCREPO_PORT'] || 8984 %>/<%= ENV['FCREPO_REST_PATH'] || 'rest' %> 5 | base_path: <%= ENV['FCREPO_BASE_PATH'] || '/dev' %> 6 | test: 7 | user: fedoraAdmin 8 | password: fedoraAdmin 9 | url: http://<%= ENV['FCREPO_HOST'] || 'localhost' %>:<%= ENV['FCREPO_TEST_PORT'] || ENV['FCREPO_PORT'] || 8986 %>/<%= ENV['FCREPO_REST_PATH'] || 'rest' %> 10 | base_path: <%= ENV['FCREPO_BASE_PATH'] || '/test' %> 11 | production: 12 | user: fedoraAdmin 13 | password: fedoraAdmin 14 | url: http://<%= ENV['FCREPO_HOST'] || 'localhost' %>:<%= ENV['FCREPO_PORT'] || 8983 %>/<%= ENV['FCREPO_REST_PATH'] || 'rest' %> 15 | base_path: <%= ENV['FCREPO_BASE_PATH'] || '/prod' %> 16 | -------------------------------------------------------------------------------- /lib/active_fedora/schema_indexing_strategy.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | ## 3 | # An extension strategy to also apply solr indexes for each property. 4 | # @note If how a field is indexed changes based on property, this would be a 5 | # good place to define that. 6 | class SchemaIndexingStrategy 7 | # @param [#index] indexer The indexer to use 8 | def initialize(indexer = Indexers::NullIndexer.instance) 9 | @indexer = indexer 10 | end 11 | 12 | # @param [ActiveFedora::Base] object The object to apply the property to. 13 | # @param [ActiveTriples::Property, #name, #to_h] property The property to define. 14 | def apply(object, property) 15 | object.property property.name, property.to_h do |index| 16 | indexer.new(property).index(index) 17 | end 18 | end 19 | 20 | private 21 | 22 | attr_reader :indexer 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/has_many.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class HasMany < CollectionAssociation # :nodoc: 3 | def self.macro 4 | :has_many 5 | end 6 | 7 | def self.valid_options(options) 8 | super + [:as, :dependent, :inverse_of] 9 | end 10 | 11 | def self.valid_dependent_options 12 | [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception] 13 | end 14 | 15 | def self.define_readers(mixin, name) 16 | super 17 | 18 | mixin.redefine_method("#{name.to_s.singularize}_ids") do 19 | association(name).ids_reader 20 | end 21 | end 22 | 23 | def self.define_writers(mixin, name) 24 | super 25 | 26 | mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids| 27 | association(name).ids_writer(ids) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/active_fedora/inheritable_accessors.rb: -------------------------------------------------------------------------------- 1 | # Similar to ActiveSupport.class_attribute but with a setter that doesn't use the #{name}= syntax 2 | # This preserves backward compatibility with the API in ActiveTriples 3 | require "active_support/core_ext/module/remove_method" 4 | 5 | module ActiveFedora 6 | module InheritableAccessors 7 | extend ActiveSupport::Concern 8 | module ClassMethods 9 | def define_inheritable_accessor(*names) 10 | names.each do |name| 11 | define_accessor(name, nil) 12 | end 13 | end 14 | 15 | private 16 | 17 | def define_accessor(name, val) 18 | singleton_class.class_eval do 19 | remove_possible_method(name) 20 | define_method(name) do |uri = nil| 21 | define_accessor(name, uri) if uri 22 | val 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/checksum_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module ActiveFedora 4 | describe Checksum do 5 | subject { described_class.new(file) } 6 | 7 | let(:uri) { ::RDF::URI("urn:sha1:bb3200c2ddaee4bd7b9a4dc1ad3e10ed886eaef1") } 8 | 9 | describe "when initialized with a file having a digest" do 10 | let(:file) { instance_double(ActiveFedora::File, digest: [uri]) } 11 | 12 | its(:uri) { is_expected.to eq(uri) } 13 | its(:value) { is_expected.to eq("bb3200c2ddaee4bd7b9a4dc1ad3e10ed886eaef1") } 14 | its(:algorithm) { is_expected.to eq("SHA1") } 15 | end 16 | 17 | describe "when initialized with a file not having a digest" do 18 | let(:file) { instance_double(ActiveFedora::File, digest: []) } 19 | 20 | its(:uri) { is_expected.to be_nil } 21 | its(:value) { is_expected.to be_nil } 22 | its(:algorithm) { is_expected.to be_nil } 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/singular_association.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class SingularAssociation < Association # :nodoc: 3 | def self.valid_options(options) 4 | super + [:dependent, :inverse_of, :required] 5 | end 6 | 7 | def self.define_accessors(model, reflection) 8 | super 9 | define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable? 10 | end 11 | 12 | def self.define_constructors(mixin, name) 13 | mixin.redefine_method("build_#{name}") do |*params| 14 | association(name).build(*params) 15 | end 16 | 17 | mixin.redefine_method("create_#{name}") do |*params| 18 | association(name).create(*params) 19 | end 20 | 21 | mixin.redefine_method("create_#{name}!") do |*params| 22 | association(name).create!(*params) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/pathing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | describe ".uri_prefix" do 5 | before do 6 | class FooHistory < ActiveFedora::Base 7 | def uri_prefix 8 | "foo" 9 | end 10 | property :title, predicate: ::RDF::Vocab::DC.title 11 | end 12 | end 13 | 14 | after do 15 | Object.send(:remove_const, :FooHistory) 16 | end 17 | 18 | subject(:history) { FooHistory.new(title: ["Root foo"]) } 19 | let(:path) { "foo" } 20 | 21 | it { is_expected.to have_uri_prefix } 22 | 23 | it "uses the root path in the uri" do 24 | expect(history.uri_prefix).to eql path 25 | end 26 | 27 | context "when the object is saved" do 28 | before { history.save } 29 | 30 | it "persists the path in the uri" do 31 | expect(history.uri.to_s).to include(path) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/indexing_service_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::IndexingService do 4 | let(:indexer) { described_class.new(object) } 5 | let(:object) { ActiveFedora::Base.new } 6 | 7 | describe "#generate_solr_document" do 8 | context "when no block is passed" do 9 | subject(:solr_doc) { indexer.generate_solr_document } 10 | it "produces a document" do 11 | expect(solr_doc['has_model_ssim']).to eq ['ActiveFedora::Base'] 12 | end 13 | end 14 | 15 | context "when a block is passed" do 16 | subject(:solr_doc) do 17 | indexer.generate_solr_document do |solr_doc| 18 | solr_doc['noid'] = '12345' 19 | end 20 | end 21 | 22 | it "produces and yield the document" do 23 | expect(solr_doc['has_model_ssim']).to eq ['ActiveFedora::Base'] 24 | expect(solr_doc['noid']).to eq '12345' 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/active_fedora/default_model_mapper.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | # Create model classifiers for resources or solr documents 3 | class DefaultModelMapper 4 | attr_reader :classifier_class, :solr_field, :predicate 5 | 6 | def initialize(classifier_class: ActiveFedora::ModelClassifier, solr_field: ActiveFedora::QueryResultBuilder::HAS_MODEL_SOLR_FIELD, predicate: ActiveFedora::RDF::Fcrepo::Model.hasModel) 7 | @classifier_class = classifier_class 8 | @solr_field = solr_field 9 | @predicate = predicate 10 | end 11 | 12 | def classifier(resource) 13 | models = if resource.respond_to? :graph 14 | resource.graph.query([nil, predicate, nil]).map { |rg| rg.object.to_s } 15 | elsif resource.respond_to? :[] 16 | resource[solr_field] || [] 17 | else 18 | [] 19 | end 20 | 21 | classifier_class.new(models) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/core/fedora_id_translator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Core::FedoraIdTranslator do 4 | describe ".call" do 5 | let(:result) { described_class.call(id) } 6 | context "when given an id" do 7 | let(:good_uri) { ActiveFedora.fedora.base_uri + "/banana" } 8 | let(:id) { "banana" } 9 | it "returns a fedora URI" do 10 | expect(result).to eq good_uri 11 | end 12 | 13 | context "with a leading slash" do 14 | let(:id) { "/banana" } 15 | it "returns a good fedora URI" do 16 | expect(result).to eq good_uri 17 | end 18 | end 19 | 20 | context "with characters that need escaping" do 21 | let(:good_uri) { ActiveFedora.fedora.base_uri + "/%5Bfrob%5D" } 22 | let(:id) { "[frob]" } 23 | it "returns a good fedora URI" do 24 | expect(result).to eq good_uri 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/active_fedora/file_persistence.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module FilePersistence 3 | extend ActiveSupport::Concern 4 | 5 | include ActiveFedora::Persistence 6 | 7 | private 8 | 9 | def _create_record(_options = {}) 10 | return false if content.nil? 11 | @ldp_source = build_ldp_binary_source 12 | ldp_source.create do |req| 13 | req.headers.merge!(ldp_headers) 14 | end 15 | refresh 16 | end 17 | 18 | def _update_record(_options = {}) 19 | return true unless content_changed? 20 | ldp_source.content = content 21 | ldp_source.update do |req| 22 | req.headers.merge!(ldp_headers) 23 | end 24 | refresh 25 | end 26 | 27 | def build_ldp_binary_source 28 | if id 29 | build_ldp_resource_via_uri(uri, content) 30 | else 31 | build_ldp_resource_via_uri(nil, content) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/model/templates/datastream.rb.erb: -------------------------------------------------------------------------------- 1 | # Generated via 2 | # `rails generate active_fedora:model <%= class_name %>` 3 | class <%= class_name %>Metadata < ActiveFedora::OmDatastream 4 | 5 | # Define a terminology for parsing this XML document 6 | # See: https://github.com/samvera/om/wiki/Tame-your-XML-with-OM 7 | # 8 | # set_terminology do |t| 9 | # t.root(path: "fields") 10 | # t.title 11 | # t.author 12 | # end 13 | 14 | 15 | # Describe what an empty document looks like 16 | # 17 | # def self.xml_template 18 | # Nokogiri::XML.parse("") 19 | # end 20 | # 21 | 22 | 23 | # "If you need to add additional attributes to the SOLR document, define the 24 | # #to_solr method and make sure to use super" 25 | # 26 | # def to_solr(solr_document={}, options={}) 27 | # super(solr_document, options) 28 | # solr_document["my_attribute_s"] = my_attribute 29 | # return solr_document 30 | # end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/id_composite.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations 2 | ## 3 | # A composite object for an array of IDs. This abstracts away the fact that an 4 | # ID might be either a relative ID or a URI to a resource. 5 | class IDComposite 6 | attr_reader :ids, :id_translator 7 | include Enumerable 8 | # @param [Array<#to_s>] ids An array of ids or URIs to convert to IDs. 9 | # @param [#call] id_translator An object to handle the conversion of a URI 10 | # to an ID. 11 | def initialize(ids, id_translator) 12 | @ids = ids 13 | @id_translator = id_translator 14 | end 15 | 16 | # @return [Array] 17 | def each 18 | ids.each do |id| 19 | yield convert(id) 20 | end 21 | end 22 | 23 | private 24 | 25 | def convert(id) 26 | if id.to_s.start_with?("http") 27 | id_translator.call(id) 28 | else 29 | id 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/unit/base_active_model_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | describe "active model methods" do 5 | class BarHistory < ActiveFedora::Base 6 | property :fubar, predicate: ::RDF::URI('http://example.com/fubar'), multiple: false 7 | property :duck, predicate: ::RDF::URI('http://example.com/duck'), multiple: false 8 | end 9 | subject(:history) { BarHistory.new } 10 | 11 | describe "attributes=" do 12 | it "sets attributes" do 13 | history.attributes = { fubar: "baz", duck: "Quack" } 14 | expect(history.fubar).to eq "baz" 15 | expect(history.duck).to eq "Quack" 16 | end 17 | end 18 | 19 | describe "update_attributes" do 20 | it "sets attributes and save" do 21 | history.update_attributes(fubar: "baz", duck: "Quack") 22 | history.reload 23 | expect(history.fubar).to eq "baz" 24 | expect(history.duck).to eq "Quack" 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/active_fedora/file/external.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::File::External 2 | HANDLING_TYPES = ['redirect', 'proxy', 'copy'].freeze 3 | 4 | def external_uri 5 | @external_uri ||= fetch_external_uri 6 | end 7 | 8 | def external_uri=(uri) 9 | @external_uri = uri 10 | end 11 | 12 | def external_handling=(handling) 13 | @external_handling = handling 14 | end 15 | 16 | def external_handling 17 | @external_handling ||= fetch_external_handling 18 | end 19 | 20 | private 21 | 22 | def fetch_external_uri 23 | return if new_record? 24 | ldp_source.head.response.headers['Content-Location'] 25 | end 26 | 27 | def fetch_external_handling 28 | return if new_record? 29 | response = ldp_source.head.response 30 | return unless response.headers.key?('Content-Location') 31 | 32 | case response.status 33 | when Net::HTTPRedirection 34 | 'redirect' 35 | when Net::HTTPSuccess 36 | 'proxy' 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/active_fedora/railtie.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class Railtie < Rails::Railtie 3 | config.app_middleware.insert_after ::ActionDispatch::Callbacks, 4 | ActiveFedora::LdpCache 5 | config.action_dispatch.rescue_responses.merge!( 6 | "ActiveFedora::ObjectNotFoundError" => :not_found 7 | ) 8 | 9 | config.eager_load_namespaces << ActiveFedora 10 | 11 | initializer 'active_fedora.autoload', before: :set_autoload_paths do |app| 12 | app.config.autoload_paths << 'app/models/datastreams' 13 | end 14 | 15 | initializer "active_fedora.logger" do 16 | ActiveSupport.on_load(:active_fedora) do 17 | self.logger = ::Rails.logger if logger.is_a? NullLogger 18 | end 19 | end 20 | 21 | generators do 22 | require( 23 | 'generators/active_fedora/config/config_generator' 24 | ) 25 | require( 26 | 'generators/active_fedora/model/model_generator' 27 | ) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/integration/json_serialization_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Objects should be serialized to JSON" do 4 | it "has json results" do 5 | expect(ActiveFedora::Base.new.to_json).to eq "{\"id\":null}" 6 | end 7 | 8 | context "with properties" do 9 | before do 10 | class Foo < ActiveFedora::Base 11 | property :title, predicate: ::RDF::Vocab::DC.title 12 | property :description, predicate: ::RDF::Vocab::DC.description, multiple: false 13 | end 14 | end 15 | 16 | after do 17 | Object.send(:remove_const, :Foo) 18 | end 19 | 20 | let(:obj) { Foo.new(title: ['My Title'], description: 'Wonderful stuff') } 21 | 22 | before { allow(obj).to receive(:id).and_return('test-123') } 23 | 24 | let(:json) { JSON.parse(obj.to_json) } 25 | 26 | it "has to_json" do 27 | expect(json['id']).to eq "test-123" 28 | expect(json['title']).to eq ["My Title"] 29 | expect(json['description']).to eq "Wonderful stuff" 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/rdf_vocab_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe ActiveFedora::RDF do 5 | describe ActiveFedora::RDF::Fcrepo do 6 | it "registers the vocabularies" do 7 | namespaces = [ 8 | "info:fedora/fedora-system:def/model#", 9 | "info:fedora/fedora-system:def/view#", 10 | "info:fedora/fedora-system:def/relations-external#", 11 | "info:fedora/fedora-system:" 12 | ] 13 | namespaces.each do |namespace| 14 | vocab = RDF::Vocabulary.find(namespace) 15 | expect(vocab.superclass).to be(RDF::StrictVocabulary) 16 | end 17 | end 18 | end 19 | describe ActiveFedora::RDF::ProjectHydra do 20 | it "registers the vocabularies" do 21 | namespaces = [ 22 | "http://projecthydra.org/ns/relations#" 23 | ] 24 | namespaces.each do |namespace| 25 | vocab = RDF::Vocabulary.find(namespace) 26 | expect(vocab.superclass).to be(RDF::StrictVocabulary) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/protwords.txt: -------------------------------------------------------------------------------- 1 | # The ASF licenses this file to You under the Apache License, Version 2.0 2 | # (the "License"); you may not use this file except in compliance with 3 | # the License. You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | #----------------------------------------------------------------------- 14 | # Use a protected word file to protect against the stemmer reducing two 15 | # unrelated words to the same base word. 16 | 17 | # Some non-words that normally won't be encountered, 18 | # just to test that they won't be stemmed. 19 | dontstems 20 | zwhacky 21 | 22 | -------------------------------------------------------------------------------- /lib/active_fedora/rdf/persistence.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module RDF 3 | # 4 | # Mixin for adding datastream persistence to an ActiveTriples::Resource 5 | # descendant so that it may be used to back an ActiveFedora::RDFDatastream. 6 | # 7 | # @see ActiveFedora::RDFDatastream.resource_class 8 | # @see ActiveFedora::RDF::ObjectResource 9 | # 10 | module Persistence 11 | extend ActiveSupport::Concern 12 | 13 | BASE_URI = 'info:fedora/'.freeze 14 | 15 | included do 16 | configure base_uri: BASE_URI unless base_uri 17 | attr_accessor :datastream 18 | end 19 | 20 | # Overrides ActiveTriples::Resource 21 | def persist! 22 | return false unless datastream&.respond_to?(:save) 23 | @persisted ||= datastream.save 24 | end 25 | 26 | # Overrides ActiveTriples::Resource 27 | def persisted? 28 | return true if frozen? && !datastream.new_record? 29 | @persisted ||= !datastream.new_record? 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/integration/gone_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | before(:all) do 5 | class ResurrectionModel < ActiveFedora::Base 6 | after_destroy :eradicate 7 | end 8 | end 9 | 10 | after(:all) do 11 | Object.send(:remove_const, :ResurrectionModel) 12 | end 13 | 14 | context "when an object is has already been deleted" do 15 | let(:ghost) do 16 | obj = described_class.create 17 | obj.destroy 18 | obj.id 19 | end 20 | it "is gone" do 21 | expect(described_class.gone?(ghost)).to be true 22 | end 23 | end 24 | 25 | context "when the id has never been used" do 26 | let(:id) { "abc123" } 27 | it "is not gone" do 28 | expect(described_class.gone?(id)).to be false 29 | end 30 | end 31 | 32 | context "when the id is in use" do 33 | let(:active) do 34 | obj = described_class.create 35 | obj.id 36 | end 37 | it "is not gone" do 38 | expect(described_class.gone?(active)).to be false 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/active_fedora/attributes/serializers.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Attributes 3 | module Serializers 4 | ## This allows you to use date_select helpers in rails views 5 | # @param [Hash] params parameters hash 6 | # @return [Hash] a parameters list with the date select parameters replaced with dates 7 | def deserialize_dates_from_form(params) 8 | dates = {} 9 | params.each do |key, value| 10 | next unless data = key.to_s.match(/^(.+)\((\d)i\)$/) 11 | dates[data[1]] ||= {} 12 | dates[data[1]][data[2]] = value 13 | params.delete(key) 14 | end 15 | dates.each do |key, value| 16 | params[key] = [value['1'], value['2'], value['3']].join('-') 17 | end 18 | params 19 | end 20 | 21 | # set a hash of attributes on the object 22 | # @param [Hash] params the properties to set on the object 23 | def attributes=(params) 24 | super(deserialize_dates_from_form(params)) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/integration/file_fixity_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Checking fixity" do 4 | before(:all) do 5 | class MockAFBase < ActiveFedora::Base 6 | has_subresource "data", autocreate: true 7 | end 8 | end 9 | 10 | after(:all) do 11 | Object.send(:remove_const, :MockAFBase) 12 | end 13 | 14 | subject do 15 | obj = MockAFBase.create 16 | obj.data.content = "some content" 17 | obj.save 18 | obj.data.check_fixity 19 | end 20 | 21 | context "with a valid resource" do 22 | it { is_expected.to be true } 23 | end 24 | context "when no uri has been set" do 25 | subject(:file) { ActiveFedora::File.new } 26 | it "raises an error" do 27 | expect { file.check_fixity }.to raise_error(ArgumentError, "You must provide a uri") 28 | end 29 | end 30 | context "with missing resource" do 31 | subject(:file) { ActiveFedora::File.new(ActiveFedora::Base.id_to_uri('123')) } 32 | it "raises an error" do 33 | expect { file.check_fixity }.to raise_error(Ldp::NotFound) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/scripts.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | user= 17 | solr_hostname=localhost 18 | solr_port=8983 19 | rsyncd_port=18983 20 | data_dir= 21 | webapp_name=solr 22 | master_host= 23 | master_data_dir= 24 | master_status_dir= 25 | -------------------------------------------------------------------------------- /spec/integration/fedora_solr_sync_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'timeout' 3 | 4 | describe "fedora_solr_sync_issues" do 5 | before :all do 6 | class ParentThing < ActiveFedora::Base 7 | has_many :things, class_name: 'ChildThing', predicate: ActiveFedora::RDF::Fcrepo::RelsExt.isPartOf 8 | end 9 | 10 | class ChildThing < ActiveFedora::Base 11 | belongs_to :parent, class_name: 'ParentThing', predicate: ActiveFedora::RDF::Fcrepo::RelsExt.isPartOf 12 | end 13 | end 14 | 15 | after :all do 16 | Object.send(:remove_const, :ChildThing) 17 | Object.send(:remove_const, :ParentThing) 18 | end 19 | 20 | subject(:child) { ChildThing.create parent: parent } 21 | let(:parent) { ParentThing.create } 22 | 23 | before { Ldp::Resource::RdfSource.new(ActiveFedora.fedora.connection, child.uri).delete } 24 | 25 | it "does not go into an infinite loop" do 26 | parent.reload 27 | expect(ActiveFedora::Base.logger).to receive(:error).with("Solr and Fedora may be out of sync:\n") 28 | expect(parent.things).to eq [] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/active_fedora/containers/container.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | # This is the base class for ldp containers, it is not an ldp:BasicContainer 3 | class Container < ActiveFedora::Base 4 | property :membership_resource, predicate: ::RDF::Vocab::LDP.membershipResource 5 | property :has_member_relation, predicate: ::RDF::Vocab::LDP.hasMemberRelation 6 | property :is_member_of_relation, predicate: ::RDF::Vocab::LDP.isMemberOfRelation 7 | property :contained, predicate: ::RDF::Vocab::LDP.contains 8 | 9 | def parent 10 | @parent || raise("Parent hasn't been set on #{self.class}") 11 | end 12 | 13 | def parent=(parent) 14 | @parent = parent 15 | self.membership_resource = [::RDF::URI(parent.uri)] 16 | end 17 | 18 | def mint_id 19 | "#{id}/#{SecureRandom.uuid}" 20 | end 21 | 22 | def self.find_or_initialize(id) 23 | find(id) 24 | rescue ActiveFedora::ObjectNotFoundError 25 | new(id: id) 26 | end 27 | 28 | private 29 | 30 | # Don't allow directly setting contained 31 | def contained=(*_args); end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/unit/indexing/map_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Indexing::Map do 4 | describe ".merge" do 5 | subject(:merged) { first_map.merge(extra) } 6 | let(:index_object1) { instance_double(described_class::IndexObject) } 7 | let(:index_object2) { instance_double(described_class::IndexObject) } 8 | let(:index_object3) { instance_double(described_class::IndexObject) } 9 | let(:first_map) { described_class.new(one: index_object1, two: index_object2) } 10 | 11 | context "with a hash" do 12 | let(:extra) { { three: index_object3 } } 13 | it "merges with a hash" do 14 | expect(merged).to be_instance_of described_class 15 | expect(merged.keys).to match_array [:one, :two, :three] 16 | end 17 | end 18 | 19 | context "with another Indexing::Map" do 20 | let(:extra) { described_class.new(three: index_object3) } 21 | it "merges with the other map" do 22 | expect(merged).to be_instance_of described_class 23 | expect(merged.keys).to match_array [:one, :two, :three] 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/fedora_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Fedora do 4 | subject(:fedora) { described_class.new(config) } 5 | describe "#authorized_connection" do 6 | describe "with SSL options" do 7 | let(:config) { 8 | { url: "https://example.com", 9 | user: "fedoraAdmin", 10 | password: "fedoraAdmin", 11 | ssl: { ca_path: '/path/to/certs' } } 12 | } 13 | specify { 14 | expect(Faraday).to receive(:new).with("https://example.com", { ssl: { ca_path: '/path/to/certs' } }).and_call_original 15 | fedora.authorized_connection 16 | } 17 | end 18 | describe "with request options" do 19 | let(:config) { 20 | { url: "https://example.com", 21 | user: "fedoraAdmin", 22 | password: "fedoraAdmin", 23 | request: { timeout: 600, open_timeout: 60 } } 24 | } 25 | specify { 26 | expect(Faraday).to receive(:new).with("https://example.com", { request: { timeout: 600, open_timeout: 60 } }).and_call_original 27 | fedora.authorized_connection 28 | } 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/unit/base_extra_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | subject(:object) { described_class.new } 5 | describe ".update_index" do 6 | before do 7 | mock_conn = instance_double(RSolr::Client) 8 | expect(mock_conn).to receive(:add) do |_, opts| 9 | expect(opts).to eq(params: { softCommit: true }) 10 | end 11 | mock_ss = instance_double(ActiveFedora::SolrService) 12 | allow(mock_ss).to receive(:conn).and_return(mock_conn) 13 | allow(ActiveFedora::SolrService).to receive(:instance).and_return(mock_ss) 14 | end 15 | 16 | it "makes the solr_document with to_solr and add it" do 17 | expect(object).to receive(:to_solr) 18 | object.update_index 19 | end 20 | end 21 | 22 | describe ".delete" do 23 | before do 24 | allow(object).to receive(:new_record?).and_return(false) 25 | allow(ActiveFedora.fedora.connection).to receive(:delete) 26 | end 27 | 28 | it "deletes object from repository and index" do 29 | expect(ActiveFedora::SolrService).to receive(:delete).with(nil) 30 | object.delete 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/active_fedora/schema.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | ## 3 | # Implement the .apply_schema method from ActiveTriples to allow for 4 | # externally defined schemas to be put on an AF::Base object. 5 | module Schema 6 | extend ActiveSupport::Concern 7 | 8 | module ClassMethods 9 | # Applies a schema to an ActiveFedora::Base. 10 | # @note The default application strategy adds no indexing hints. You may 11 | # want to implement a different strategy if you want to set values on the 12 | # property reflection. 13 | # @param schema [ActiveTriples::Schema] The schema to apply. 14 | # @param strategy [#apply] The strategy to use for applying the schema. 15 | # @example Apply a schema and index everything as symbol. 16 | # apply_schema MySchema, ActiveFedora::SchemaIndexingStrategy.new( 17 | # ActiveFedora::GlobalIndexer.new(:symbol) 18 | # ) 19 | def apply_schema(schema, strategy = ActiveFedora::SchemaIndexingStrategy.new) 20 | schema.properties.each do |property| 21 | strategy.apply(self, property) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/integration/marshal_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Marshalling and loading" do 4 | before do 5 | class Post < ActiveFedora::Base 6 | has_many :comments 7 | property :text, predicate: ::RDF::URI('http://example.com/text') 8 | end 9 | 10 | class Comment < ActiveFedora::Base 11 | belongs_to :post, predicate: ::RDF::URI('http://example.com/post') 12 | end 13 | end 14 | 15 | after do 16 | Object.send(:remove_const, :Post) 17 | Object.send(:remove_const, :Comment) 18 | end 19 | 20 | context "persisted records" do 21 | let(:post) { Post.create(text: ['serialize me']) } 22 | it "marshals them" do 23 | marshalled = Marshal.dump(post) 24 | loaded = Marshal.load(marshalled) 25 | 26 | expect(loaded.attributes).to eq post.attributes 27 | end 28 | end 29 | 30 | context "with associations" do 31 | let(:post) { Post.create(comments: [Comment.new]) } 32 | it "marshals associations" do 33 | marshalled = Marshal.dump(post) 34 | loaded = Marshal.load(marshalled) 35 | 36 | expect(loaded.comments.size).to eq 1 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/record_composite.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations 2 | ## 3 | # A Composite for records - currently only supports delete interface. 4 | # The goal is to push commands down to the containing records. 5 | class RecordComposite 6 | attr_reader :records 7 | include Enumerable 8 | def initialize(records:) 9 | @records = records 10 | end 11 | 12 | def each 13 | records.each do |record| 14 | yield record 15 | end 16 | end 17 | 18 | def delete 19 | each(&:delete) 20 | end 21 | 22 | ## 23 | # A Repository which returns a composite from #find instead of a single 24 | # record. Delegates find to a base repository. 25 | class Repository 26 | attr_reader :base_repository 27 | delegate :translate_uri_to_id, to: :base_repository 28 | def initialize(base_repository:) 29 | @base_repository = base_repository 30 | end 31 | 32 | def find(ids) 33 | records = ids.map do |id| 34 | base_repository.find(id) 35 | end 36 | RecordComposite.new(records: records) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/active_fedora/versions_graph.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class VersionsGraph < ::RDF::Graph 3 | def all(_opts = {}) 4 | versions = fedora_versions 5 | versions.sort_by { |version| DateTime.parse(version.created) } 6 | rescue ArgumentError, NoMethodError 7 | raise ActiveFedora::VersionLacksCreateDate 8 | end 9 | 10 | delegate :first, to: :all 11 | 12 | delegate :last, to: :all 13 | 14 | def with_datetime(datetime) 15 | all.each do |version| 16 | return version if version.created == datetime 17 | end 18 | end 19 | 20 | def versions 21 | query({ predicate: ::RDF::Vocab::LDP.contains }) 22 | end 23 | 24 | private 25 | 26 | class ResourceVersion 27 | attr_accessor :uri, :created 28 | end 29 | 30 | def version_from_resource(statement) 31 | version = ResourceVersion.new 32 | version.uri = statement.object 33 | version.created = statement.object.to_s.split("fcr:versions/")[1] 34 | version 35 | end 36 | 37 | def fedora_versions 38 | versions.map { |statement| version_from_resource(statement) } 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/unit/file/streaming_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::File::Streaming do 4 | let(:test_class) do 5 | tc = Class.new 6 | tc.send(:include, described_class) 7 | tc 8 | end 9 | let(:streamer) do 10 | streamer = test_class.new 11 | allow(streamer).to receive(:uri).and_return(uri) 12 | streamer 13 | end 14 | let(:http_client) { instance_double("Faraday::Connection") } 15 | 16 | before do 17 | allow(http_client).to receive(:get).and_return(nil) 18 | end 19 | 20 | context "without ssl" do 21 | let(:uri) { "http://localhost/file/1" } 22 | 23 | it do 24 | expect(Faraday).to receive(:new).with(ActiveFedora.fedora.host, {}).and_return(http_client) 25 | streamer.stream.each 26 | end 27 | end 28 | 29 | context "with ssl" do 30 | let(:uri) { "https://localhost/file/1" } 31 | 32 | before do 33 | allow(ActiveFedora.fedora).to receive(:ssl_options).and_return(true) 34 | end 35 | 36 | it do 37 | expect(Faraday).to receive(:new).with(ActiveFedora.fedora.host, hash_including(ssl: true)).and_return(http_client) 38 | streamer.stream.each 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/active_fedora/file_path_builder.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class FilePathBuilder 3 | # Builds a relative path for a file 4 | def self.build(digital_object, name, prefix) 5 | name = nil if name == '' 6 | prefix ||= 'DS' 7 | name || generate_dsid(digital_object, prefix) 8 | end 9 | 10 | # return a valid dsid that is not currently in use. Uses a prefix (default "DS") and an auto-incrementing integer 11 | # Example: if there are already datastreams with IDs DS1 and DS2, this method will return DS3. If you specify FOO as the prefix, it will return FOO1. 12 | def self.generate_dsid(digital_object, prefix) 13 | return unless digital_object 14 | matches = digital_object.attached_files.keys.map do |d| 15 | data = /^#{prefix}(\d+)$/.match(d) 16 | data && data[1].to_i 17 | end.compact 18 | val = matches.empty? ? 1 : matches.max + 1 19 | format_dsid(prefix, val) 20 | end 21 | 22 | ### Provided so that an application can override how generated ids are formatted (e.g DS01 instead of DS1) 23 | def self.format_dsid(prefix, suffix) 24 | format "%s%i", prefix, suffix 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/integration/has_subresource_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Base do 4 | before do 5 | class Source < ActiveFedora::Base 6 | has_subresource :sub_resource, class_name: "Source" 7 | property :title, predicate: ::RDF::Vocab::DC.title, multiple: false 8 | end 9 | end 10 | after do 11 | Object.send(:remove_const, :Source) 12 | end 13 | 14 | describe "contains relationships" do 15 | it "is able to have RDF sources" do 16 | s = Source.new 17 | s.sub_resource.title = "Test" 18 | expect(s.sub_resource).not_to be_persisted 19 | expect { s.save }.not_to raise_error 20 | s.reload 21 | expect(s.sub_resource.title).to eq "Test" 22 | expect(s.sub_resource.uri).to eq s.uri.to_s + "/sub_resource" 23 | end 24 | it "is able to add RDF sources" do 25 | s = Source.create 26 | s.sub_resource.title = "Test" 27 | expect(s.sub_resource).not_to be_persisted 28 | expect { s.save }.not_to raise_error 29 | s.reload 30 | expect(s.sub_resource.title).to eq "Test" 31 | expect(s.sub_resource.uri).to eq s.uri.to_s + "/sub_resource" 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/integration/persistence_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "persisting objects" do 4 | describe "#create!" do 5 | before do 6 | class MockAFBaseRelationship < ActiveFedora::Base 7 | property :name, predicate: ::RDF::Vocab::DC.title, multiple: false 8 | validates :name, presence: true 9 | end 10 | end 11 | 12 | after do 13 | Object.send(:remove_const, :MockAFBaseRelationship) 14 | end 15 | 16 | it "validates" do 17 | expect { MockAFBaseRelationship.create! }.to raise_error ActiveFedora::RecordInvalid, "Validation failed: Name can't be blank" 18 | end 19 | end 20 | 21 | describe "#save" do 22 | context "With undefined contains associations" do 23 | let(:f1) { ActiveFedora::Base.create } 24 | let!(:f2) { ActiveFedora::Base.create(id: "#{f1.id}/part2") } 25 | 26 | before do 27 | f1.reload # so it learns about f2 28 | end 29 | 30 | it "doesn't load the children" do 31 | allow(f1).to receive(:update_index) # solrizing can load the attached files. 32 | expect(ActiveFedora::File).not_to receive(:new) 33 | f1.save 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/belongs_to.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class BelongsTo < SingularAssociation # :nodoc: 3 | def self.macro 4 | :belongs_to 5 | end 6 | 7 | def self.valid_options(options) 8 | super + [:optional] 9 | end 10 | 11 | def self.valid_dependent_options 12 | [:destroy, :delete] 13 | end 14 | 15 | def self.validate_options(options) 16 | super 17 | raise "You must specify a predicate for #{name}" unless options[:predicate] 18 | raise ArgumentError, "Predicate must be a kind of RDF::URI" unless options[:predicate].is_a?(RDF::URI) 19 | end 20 | 21 | def self.define_validations(model, reflection) 22 | reflection.options[:optional] = !reflection.options.delete(:required) if reflection.options.key?(:required) 23 | 24 | required = if reflection.options[:optional].nil? 25 | model.belongs_to_required_by_default 26 | else 27 | !reflection.options[:optional] 28 | end 29 | 30 | super 31 | 32 | model.validates_presence_of reflection.name, message: :required if required 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/active_fedora/inheritance.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Inheritance 3 | extend ActiveSupport::Concern 4 | 5 | module ClassMethods 6 | # Returns the class descending directly from ActiveFedora::Base, or 7 | # an abstract class, if any, in the inheritance hierarchy. 8 | # 9 | # If A extends ActiveFedora::Base, A.base_class will return A. If B descends from A 10 | # through some arbitrarily deep hierarchy, B.base_class will return A. 11 | # 12 | # If B < A and C < B and if A is an abstract_class then both B.base_class 13 | # and C.base_class would return B as the answer since A is an abstract_class. 14 | def base_class 15 | return File if self <= File 16 | 17 | raise ActiveFedoraError, "#{name} doesn't belong in a hierarchy descending from ActiveFedora" unless self <= Base 18 | 19 | if self == Base || superclass == Base || superclass.abstract_class? 20 | self 21 | else 22 | superclass.base_class 23 | end 24 | end 25 | 26 | # Abstract classes can't have default scopes. 27 | def abstract_class? 28 | self == Base 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/indirectly_contains.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class IndirectlyContains < CollectionAssociation # :nodoc: 3 | def self.macro 4 | :indirectly_contains 5 | end 6 | 7 | def self.valid_options(options) 8 | super + [:has_member_relation, :is_member_of_relation, :inserted_content_relation, :foreign_key, :through] - [:predicate] 9 | end 10 | 11 | def self.define_readers(mixin, name) 12 | super 13 | 14 | mixin.redefine_method("#{name.to_s.singularize}_ids") do 15 | association(name).ids_reader 16 | end 17 | end 18 | 19 | def self.validate_options(options) 20 | super 21 | 22 | raise ArgumentError, "You must specify a predicate for #{name}" if !options[:has_member_relation] && !options[:is_member_of_relation] 23 | raise ArgumentError, "Predicate must be a kind of RDF::URI" if !options[:has_member_relation].is_a?(RDF::URI) && !options[:is_member_of_relation].is_a?(RDF::URI) 24 | raise ArgumentError, "Missing :through option" unless options[:through] 25 | raise ArgumentError, "Missing :foreign_key option" unless options[:foreign_key] 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/active_fedora/ldp_resource.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module LdpResourceAddons 3 | extend ActiveSupport::Concern 4 | module ClassMethods 5 | def graph_class 6 | ActiveTriples::Resource 7 | end 8 | end 9 | 10 | def build_empty_graph 11 | graph_class.new(subject_uri) 12 | end 13 | 14 | def graph_class 15 | self.class.graph_class 16 | end 17 | 18 | # Don't dump @client, it has a proc and thus can't be serialized. 19 | def marshal_dump 20 | (instance_variables - [:@client]).map { |name| [name, instance_variable_get(name)] } 21 | end 22 | 23 | def marshal_load(data) 24 | data.each { |name, val| instance_variable_set(name, val) } 25 | end 26 | 27 | private 28 | 29 | def response_as_graph(resp) 30 | graph_class.new(subject_uri, data: resp.graph.data) 31 | end 32 | end 33 | 34 | class LdpResource < Ldp::Resource::RdfSource 35 | include LdpResourceAddons 36 | end 37 | 38 | class IndirectContainerResource < Ldp::Container::Indirect 39 | include LdpResourceAddons 40 | end 41 | 42 | class DirectContainerResource < Ldp::Container::Direct 43 | include LdpResourceAddons 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/active_fedora/with_metadata.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module WithMetadata 3 | extend ActiveSupport::Concern 4 | extend ActiveSupport::Autoload 5 | 6 | autoload :MetadataNode 7 | autoload :SweetJPLTerms 8 | autoload :DefaultStrategy 9 | autoload :DefaultSchema 10 | autoload :DefaultMetadataClassFactory 11 | 12 | included do 13 | class_attribute :metadata_class_factory 14 | self.metadata_class_factory = DefaultMetadataClassFactory 15 | end 16 | 17 | def metadata_node 18 | @metadata_node ||= self.class.metadata_schema.new(self) 19 | end 20 | 21 | def create_or_update(*) 22 | return unless super && !new_record? 23 | # TODOs captured as https://github.com/samvera/active_fedora/issues/1331 24 | metadata_node.metadata_uri = described_by # TODO: only necessary if the URI was < > before 25 | metadata_node.save # TODO: if changed? 26 | end 27 | 28 | module ClassMethods 29 | def metadata(&block) 30 | @metadata_schema = metadata_class_factory.build(self, &block) 31 | end 32 | 33 | def metadata_schema 34 | @metadata_schema ||= metadata_class_factory.build(self) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/active_fedora/rspec_matchers/belong_to_associated_active_fedora_object_matcher.rb: -------------------------------------------------------------------------------- 1 | # RSpec matcher to spec delegations. 2 | RSpec::Matchers.define :belong_to_associated_active_fedora_object do |association_name| 3 | match do |subject| 4 | @association_name = association_name 5 | if @association_name.nil? || @expected_object.nil? 6 | raise( 7 | ArgumentError, 8 | "expect(subject).to belong_to_associated_active_fedora_object().with_object()" 9 | ) 10 | end 11 | 12 | @subject = subject.class.find(subject.id) 13 | @actual_object = @subject.send(@association_name) 14 | 15 | @expected_object == @actual_object 16 | end 17 | 18 | chain(:with_object) { |object| @expected_object = object } 19 | 20 | description do 21 | "#{@subject.class} ID=#{@subject.id} association: #{@association_name.inspect} matches ActiveFedora" 22 | end 23 | 24 | failure_message do |_text| 25 | "expected #{@subject.class} ID=#{@subject.id} association: #{@association_name.inspect} to match" 26 | end 27 | 28 | failure_message_when_negated do |_text| 29 | "expected #{@subject.class} ID=#{@subject.id} association: #{@association_name.inspect} to NOT match" 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/active_fedora/clean_connection.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class CleanConnection < SimpleDelegator 3 | def get(*args) 4 | result = __getobj__.get(*args) do |req| 5 | prefer_headers = Ldp::PreferHeaders.new(req.headers["Prefer"]) 6 | prefer_headers.omit = prefer_headers.omit | omit_uris 7 | req.headers["Prefer"] = prefer_headers.to_s 8 | end 9 | CleanResult.new(result) 10 | end 11 | 12 | private 13 | 14 | def omit_uris 15 | [ 16 | "http://fedora.info/definitions/fcrepo#ServerManaged", 17 | ::RDF::Vocab::Fcrepo4.ServerManaged, 18 | ::RDF::Vocab::LDP.PreferContainment, 19 | ::RDF::Vocab::LDP.PreferEmptyContainer, 20 | ::RDF::Vocab::LDP.PreferMembership 21 | ] 22 | end 23 | 24 | class CleanResult < SimpleDelegator 25 | def graph 26 | @graph ||= clean_graph 27 | end 28 | 29 | private 30 | 31 | def clean_graph 32 | __getobj__.graph.delete(has_model_query) 33 | __getobj__.graph 34 | end 35 | 36 | def has_model_query 37 | [nil, ActiveFedora::RDF::Fcrepo::Model.hasModel, nil] 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/basic_contains_association.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Associations 3 | class BasicContainsAssociation < ContainsAssociation # :nodoc: 4 | def find_target 5 | uris = owner.resource.query({ predicate: options[:predicate] }) 6 | .map { |r| r.object.to_s } 7 | 8 | uris.map { |object_uri| klass.find(klass.uri_to_id(object_uri)) } 9 | end 10 | 11 | def insert_record(record, force = true, validate = true) 12 | record.base_path_for_resource = owner.uri.to_s 13 | super 14 | end 15 | 16 | def add_to_target(record, skip_callbacks = false) 17 | record.base_path_for_resource = owner.uri.to_s 18 | super 19 | end 20 | 21 | def reset 22 | # Update the membership triples (and no other triples) on the the owner's resource 23 | if owner.persisted? 24 | pattern = ::RDF::Query::Pattern.new(predicate: options[:predicate]) 25 | new_resource = ActiveFedora::Base.uncached do 26 | owner.dup.reload.resource 27 | end 28 | owner.resource.delete_insert([pattern], new_resource.query(pattern)) 29 | end 30 | super 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/active_fedora/ldp_cache.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | # = Active Fedora Ldp Cache 3 | class LdpCache 4 | module ClassMethods 5 | # Enable the query cache within the block if Active Fedora is configured. 6 | # If it's not, it will execute the given block. 7 | def cache(&block) 8 | connection = ActiveFedora.fedora.connection 9 | connection.cache(&block) 10 | end 11 | 12 | # Disable the query cache within the block if Active Fedora is configured. 13 | # If it's not, it will execute the given block. 14 | def uncached(&block) 15 | ActiveFedora.fedora.connection.uncached(&block) 16 | end 17 | end 18 | 19 | def initialize(app) 20 | @app = app 21 | end 22 | 23 | def call(env) 24 | ActiveFedora.fedora.connection.enable_cache! 25 | 26 | response = @app.call(env) 27 | response[2] = Rack::BodyProxy.new(response[2]) do 28 | reset_cache_settings 29 | end 30 | 31 | response 32 | ensure 33 | reset_cache_settings 34 | end 35 | 36 | private 37 | 38 | def reset_cache_settings 39 | ActiveFedora.fedora.connection.clear_cache 40 | ActiveFedora.fedora.connection.disable_cache! 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/model/templates/model_spec.rb.erb: -------------------------------------------------------------------------------- 1 | # Generated via 2 | # `rails generate active_fedora:model <%= class_name %>` 3 | require 'rails_helper' 4 | require 'active_fedora/test_support' 5 | 6 | describe <%= class_name %> do 7 | it_behaves_like 'An ActiveModel' 8 | include ActiveFedora::TestSupport 9 | subject { <%= class_name %>.new } 10 | 11 | describe "when persisted to fedora" do 12 | before { subject.save! } 13 | after { subject.destroy } 14 | it 'should exist' do 15 | expect(<%= class_name %>.exists?(subject.id)).to be true 16 | end 17 | end 18 | 19 | <% if options['datastream'] %> 20 | it 'should have a descMetadata datastream' do 21 | expect(subject.descMetadata).to be_kind_of <%= options['descMetadata'] ? options['descMetadata'] : "#{class_name}Metadata" %> 22 | end 23 | <% else %> 24 | it 'should have a title' do 25 | subject.title = ['War and Peace'] 26 | expect(subject.title).to eq ['War and Peace'] 27 | end 28 | 29 | describe "#to_solr" do 30 | subject { <%= class_name %>.new(title: ['War and Peace']).to_solr } 31 | 32 | it 'should have a title' do 33 | expect(subject['title_tesim']).to eq ['War and Peace'] 34 | end 35 | end 36 | <% end %> 37 | 38 | end 39 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Andrew Myers afred 2 | Chris Beer 3 | Chris Beer 4 | Chris Beer 5 | Chris Colvard cjcolvar 6 | David Chandek-Stark dchandekstark 7 | Justin Coyne 8 | Justin Coyne 9 | Justin Coyne jcoyne 10 | Justin Coyne 11 | Jesse Keck 12 | Jesse Keck 13 | Mark Bussey mark-dce 14 | Matt Zumwalt flyingzumwalt 15 | Richard Johnson rickjohnson 16 | Richard Johnson rjohns14 17 | Richard Johnson rjohns14@github.com 18 | Michael B. Klein 19 | -------------------------------------------------------------------------------- /lib/active_fedora/log_subscriber.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class LogSubscriber < ActiveSupport::LogSubscriber 3 | def initialize 4 | super 5 | @odd = false 6 | end 7 | 8 | # rubocop:disable Style/IfInsideElse 9 | def ldp(event) 10 | return unless logger.debug? 11 | 12 | payload = event.payload 13 | 14 | name = "#{payload[:name]} (#{event.duration.round(1)}ms)" 15 | id = payload[:id] || "[no id]" 16 | 17 | if ActiveSupport.version >= Gem::Version.new('7.1.0') 18 | if odd? 19 | name = color(name, CYAN, bold: true) 20 | id = color(id, nil, bold: true) 21 | else 22 | name = color(name, MAGENTA, bold: true) 23 | end 24 | else 25 | if odd? 26 | name = color(name, CYAN, true) 27 | id = color(id, nil, true) 28 | else 29 | name = color(name, MAGENTA, true) 30 | end 31 | end 32 | 33 | debug " #{name} #{id} Service: #{payload[:ldp_service]}" 34 | end 35 | # rubocop:enable Style/IfInsideElse 36 | 37 | def odd? 38 | @odd = !@odd 39 | end 40 | 41 | def logger 42 | ActiveFedora::Base.logger 43 | end 44 | end 45 | end 46 | 47 | ActiveFedora::LogSubscriber.attach_to :active_fedora 48 | -------------------------------------------------------------------------------- /spec/unit/loadable_from_json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::LoadableFromJson::SolrBackedResource do 4 | before do 5 | class Foo < ActiveFedora::Base 6 | belongs_to :bar, predicate: ::RDF::Vocab::DC.extent 7 | end 8 | 9 | class Bar < ActiveFedora::Base 10 | end 11 | end 12 | 13 | after do 14 | Object.send(:remove_const, :Foo) 15 | Object.send(:remove_const, :Bar) 16 | end 17 | 18 | let(:resource) { described_class.new(Foo) } 19 | 20 | before do 21 | resource.insert [nil, ::RDF::Vocab::DC.extent, RDF::URI('http://example.org/123')] 22 | end 23 | 24 | describe "#query" do 25 | describe "a known relationship" do 26 | subject(:resources) { resource.query({ predicate: ::RDF::Vocab::DC.extent }) } 27 | 28 | it "is enumerable" do 29 | expect(resources.map(&:object)).to eq [RDF::URI('http://example.org/123')] 30 | end 31 | end 32 | 33 | describe "a unknown relationship" do 34 | subject(:resources) { resource.query({ predicate: ::RDF::Vocab::DC.accrualPeriodicity }) } 35 | it "raises an error" do 36 | expect { resources }.to raise_error "Unable to find reflection for http://purl.org/dc/terms/accrualPeriodicity in Foo" 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/active_fedora/with_metadata/default_metadata_class_factory.rb: -------------------------------------------------------------------------------- 1 | # This builds classes for metadata nodes (nodes that describe a binary) 2 | module ActiveFedora::WithMetadata 3 | class DefaultMetadataClassFactory 4 | class_attribute :metadata_base_class, :file_metadata_schemas, :file_metadata_strategy 5 | self.metadata_base_class = MetadataNode 6 | self.file_metadata_schemas = [DefaultSchema] 7 | self.file_metadata_strategy = DefaultStrategy 8 | 9 | class << self 10 | def build(parent, &block) 11 | create_class(parent).tap do |resource_class| 12 | file_metadata_schemas.each do |schema| 13 | resource_class.apply_schema(schema, file_metadata_strategy) 14 | end 15 | resource_class.exec_block(&block) if block_given? 16 | end 17 | end 18 | 19 | private 20 | 21 | # Make a subclass of MetadataNode named GeneratedMetadataSchema and set its 22 | # parent_class attribute to have the value of the current class. 23 | def create_class(parent_klass) 24 | Class.new(metadata_base_class).tap do |klass| 25 | parent_klass.const_set(:GeneratedMetadataSchema, klass) 26 | klass.parent_class = parent_klass 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/integration/nested_hash_resources_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "nested hash resources" do 4 | before do 5 | class NestedResource < ActiveTriples::Resource 6 | property :title, predicate: ::RDF::Vocab::DC.title 7 | ## Necessary to get AT to create hash URIs. 8 | def initialize(uri, parent) 9 | if uri.try(:node?) 10 | uri = RDF::URI("#nested_#{uri.to_s.gsub('_:', '')}") 11 | elsif uri.start_with?("#") 12 | uri = RDF::URI(uri) 13 | end 14 | super 15 | end 16 | 17 | def final_parent 18 | parent 19 | end 20 | end 21 | 22 | class ExampleOwner < ActiveFedora::Base 23 | property :relation, predicate: ::RDF::Vocab::DC.relation, class_name: NestedResource 24 | accepts_nested_attributes_for :relation 25 | end 26 | end 27 | after do 28 | Object.send(:remove_const, :NestedResource) 29 | Object.send(:remove_const, :ExampleOwner) 30 | end 31 | it "is able to nest resources" do 32 | obj = ExampleOwner.new 33 | obj.attributes = { 34 | relation_attributes: [ 35 | { 36 | title: "Test" 37 | } 38 | ] 39 | } 40 | obj.save! 41 | 42 | expect(obj.reload.relation.first.title).to eq ["Test"] 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/active_fedora/ldp_resource_service.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class LdpResourceService 3 | attr_reader :connection 4 | 5 | def initialize(conn) 6 | @connection = conn 7 | end 8 | 9 | def build(klass, id) 10 | resource_klass = resource_klass(klass) 11 | if id 12 | resource_klass.new(connection, to_uri(klass, id)) 13 | else 14 | parent_uri = ActiveFedora.fedora.host + ActiveFedora.fedora.base_path 15 | resource_klass.new(connection, nil, nil, parent_uri) 16 | end 17 | end 18 | 19 | def resource_klass(klass) 20 | if klass <= ActiveFedora::IndirectContainer 21 | IndirectContainerResource 22 | elsif klass <= ActiveFedora::DirectContainer 23 | DirectContainerResource 24 | else 25 | LdpResource 26 | end 27 | end 28 | 29 | def build_resource_under_path(graph, parent_uri) 30 | parent_uri ||= ActiveFedora.fedora.host + ActiveFedora.fedora.base_path 31 | LdpResource.new(connection, nil, graph, parent_uri) 32 | end 33 | 34 | def update(change_set, klass, id) 35 | SparqlInsert.new(change_set.changes).execute(to_uri(klass, id)) 36 | end 37 | 38 | private 39 | 40 | def to_uri(klass, id) 41 | klass.id_to_uri(id) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/admin-extra.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | -------------------------------------------------------------------------------- /spec/unit/property_predicate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | describe ".property" do 5 | context "when the same predicate is used for two properties" do 6 | let(:warningMsg) { "Same predicate (http://purl.org/dc/terms/title) used for properties title1 and title2" } 7 | 8 | it "warns" do 9 | # Note that the expect test must be before the class is parsed. 10 | expect(described_class.logger).to receive(:warn).with(warningMsg) 11 | 12 | module TestModel1 13 | class Book < ActiveFedora::Base 14 | property :title1, predicate: ::RDF::Vocab::DC.title 15 | property :title2, predicate: ::RDF::Vocab::DC.title 16 | end 17 | end 18 | end 19 | end 20 | 21 | context "when properties are created with different predicates" do 22 | it "does not warn" do 23 | # Note that the expect test must be before the class is parsed. 24 | expect(described_class.logger).to_not receive(:warn) 25 | 26 | module TestModel2 27 | class Book < ActiveFedora::Base 28 | property :title1, predicate: ::RDF::Vocab::DC.title 29 | property :title2, predicate: ::RDF::Vocab::DC.creator 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/synonyms.txt: -------------------------------------------------------------------------------- 1 | # The ASF licenses this file to You under the Apache License, Version 2.0 2 | # (the "License"); you may not use this file except in compliance with 3 | # the License. You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | #----------------------------------------------------------------------- 14 | #some test synonym mappings unlikely to appear in real input text 15 | aaa => aaaa 16 | bbb => bbbb1 bbbb2 17 | ccc => cccc1,cccc2 18 | a\=>a => b\=>b 19 | a\,a => b\,b 20 | fooaaa,baraaa,bazaaa 21 | 22 | # Some synonym groups specific to this example 23 | GB,gib,gigabyte,gigabytes 24 | MB,mib,megabyte,megabytes 25 | Television, Televisions, TV, TVs 26 | #notice we use "gib" instead of "GiB" so any WordDelimiterFilter coming 27 | #after us won't split it into two words. 28 | 29 | # Synonym mappings can be used for spelling correction too 30 | pixima => pixma 31 | 32 | -------------------------------------------------------------------------------- /lib/active_fedora/indexing/map.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | module ActiveFedora::Indexing 3 | # This is a description of how properties should map to indexing strategies 4 | # e.g. 'creator_name' => 5 | class Map 6 | extend Forwardable 7 | def_delegators :@hash, :[], :[]=, :each, :keys 8 | 9 | def initialize(hash = {}) 10 | @hash = hash 11 | end 12 | 13 | def dup 14 | self.class.new(to_hash) 15 | end 16 | 17 | def merge(new_hash) 18 | self.class.new(to_hash.merge(new_hash)) 19 | end 20 | 21 | def to_hash 22 | @hash.deep_dup 23 | end 24 | 25 | # this enables a cleaner API for solr integration 26 | class IndexObject 27 | attr_accessor :data_type, :behaviors, :term 28 | attr_reader :key 29 | 30 | def initialize(name, behaviors: [], &_block) 31 | @behaviors = behaviors 32 | @data_type = :string 33 | @key = name 34 | yield self if block_given? 35 | end 36 | 37 | def as(*args) 38 | @term = args.last.is_a?(Hash) ? args.pop : {} 39 | @behaviors = args 40 | end 41 | 42 | def type(sym) 43 | @data_type = sym 44 | end 45 | 46 | def dup 47 | self.class.new(@key) do |idx| 48 | idx.behaviors = @behaviors.dup 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/unit/code_configurator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'config_helper' 3 | 4 | describe ActiveFedora::FileConfigurator do 5 | before :all do 6 | class TestConfigurator 7 | attr_reader :fedora_config, :solr_config 8 | 9 | def init(options = {}) 10 | @fedora_config = options[:fedora_config] 11 | @solr_config = options[:solr_config] 12 | end 13 | end 14 | 15 | @config_params = { 16 | fedora_config: { url: 'http://codeconfig.example.edu/fedora/', user: 'fedoraAdmin', password: 'configurator', cert_file: '/path/to/cert/file' }, 17 | solr_config: { url: 'http://codeconfig.example.edu/solr/' } 18 | } 19 | end 20 | 21 | before do 22 | ActiveFedora.configurator = TestConfigurator.new 23 | end 24 | 25 | after :all do 26 | unstub_rails 27 | # Restore to default fedora configs 28 | ActiveFedora.configurator = described_class.new 29 | restore_spec_configuration 30 | end 31 | 32 | it "initializes from code" do 33 | expect(Psych).to receive(:load).never 34 | expect(File).to receive(:exists?).never 35 | expect(File).to receive(:read).never 36 | expect(File).to receive(:open).never 37 | ActiveFedora.init(@config_params) 38 | expect(ActiveFedora.fedora_config.credentials).to eq @config_params[:fedora_config] 39 | expect(ActiveFedora.solr_config).to eq @config_params[:solr_config] 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/active_fedora/cleaner.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Cleaner 3 | def self.clean! 4 | cleanout_fedora 5 | reinitialize_repo 6 | cleanout_solr 7 | end 8 | 9 | def self.cleanout_fedora 10 | delete_root_resource 11 | delete_tombstone 12 | rescue Ldp::HttpError => exception 13 | log "#cleanout_fedora in spec_helper.rb raised #{exception}" 14 | end 15 | 16 | def self.delete_root_resource 17 | connection.delete(root_resource_path) 18 | rescue Ldp::Gone 19 | end 20 | 21 | def self.delete_tombstone 22 | connection.delete(tombstone_path) 23 | end 24 | 25 | def self.tombstone_path 26 | root_resource_path + "/fcr:tombstone" 27 | end 28 | 29 | def self.root_resource_path 30 | ActiveFedora.fedora.root_resource_path 31 | end 32 | 33 | def self.connection 34 | ActiveFedora.fedora.connection 35 | end 36 | 37 | def self.solr_connection 38 | ActiveFedora::SolrService.instance&.conn 39 | end 40 | 41 | def self.cleanout_solr 42 | restore_spec_configuration if solr_connection.nil? 43 | solr_connection.delete_by_query('*:*', params: { 'softCommit' => true }) 44 | end 45 | 46 | def self.reinitialize_repo 47 | ActiveFedora::Fedora.reset! 48 | end 49 | 50 | def self.log(message) 51 | ActiveFedora::Base.logger.debug message 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/integration/query_result_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::QueryResultBuilder do 4 | describe "#reify_solr_results" do 5 | before do 6 | class FooObject < ActiveFedora::Base 7 | def self.id_namespace 8 | "foo" 9 | end 10 | end 11 | end 12 | 13 | let(:test_object) { ActiveFedora::Base.create } 14 | let(:foo_object) { FooObject.create } 15 | 16 | after do 17 | Object.send(:remove_const, :FooObject) 18 | end 19 | 20 | it "returns an array of objects that are of the class stored in active_fedora_model_s" do 21 | query = ActiveFedora::SolrQueryBuilder.construct_query_for_ids([test_object.id, foo_object.id]) 22 | solr_result = ActiveFedora::SolrService.query(query, rows: 10) 23 | result = described_class.reify_solr_results(solr_result) 24 | expect(result.length).to eq 2 25 | result.each do |r| 26 | expect((r.class == ActiveFedora::Base || r.class == FooObject)).to be true 27 | end 28 | end 29 | 30 | it '#reifies a lightweight object as a new instance' do 31 | query = ActiveFedora::SolrQueryBuilder.construct_query_for_ids([foo_object.id]) 32 | solr_result = ActiveFedora::SolrService.query(query, rows: 10) 33 | result = described_class.reify_solr_results(solr_result, load_from_solr: true) 34 | expect(result.first).to be_instance_of FooObject 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/sparql_insert_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::SparqlInsert do 4 | subject(:sparql_insert) { described_class.new(change_set.changes) } 5 | let(:change_set) { ActiveFedora::ChangeSet.new(base, base.resource, base.changed_attributes.keys) } 6 | 7 | context "with a changed object" do 8 | before do 9 | class Library < ActiveFedora::Base 10 | end 11 | 12 | class Book < ActiveFedora::Base 13 | belongs_to :library, predicate: ActiveFedora::RDF::Fcrepo::RelsExt.hasConstituent 14 | property :title, predicate: ::RDF::Vocab::DC.title 15 | end 16 | 17 | base.library_id = 'foo' 18 | base.title = ['bar'] 19 | end 20 | after do 21 | Object.send(:remove_const, :Library) 22 | Object.send(:remove_const, :Book) 23 | end 24 | 25 | let(:base) { Book.create } 26 | 27 | it "returns the string" do 28 | expect(sparql_insert.build).to eq "DELETE { <> ?change . }\n WHERE { <> ?change . } ;\nDELETE { <> ?change . }\n WHERE { <> ?change . } ;\nINSERT { \n<> <#{ActiveFedora.fedora.host}/test/foo> .\n<> \"bar\" .\n}\n WHERE { }" 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/model/model_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | 3 | module ActiveFedora 4 | class ModelGenerator < Rails::Generators::NamedBase 5 | source_root ::File.expand_path('../templates', __FILE__) 6 | check_class_collision 7 | 8 | class_option :directory, type: :string, default: 'models', desc: "Which directory to generate? (i.e. app/DIRECTORY)" 9 | class_option :datastream_directory, type: :string, default: 'models/datastreams', desc: "Which datastream directory to generate? (i.e. models/datastreams)" 10 | class_option :has_subresource, type: :string, default: nil, desc: "Name a file to attach" 11 | class_option :datastream, type: :string, default: nil, desc: "Name a metadata datastream to create" 12 | 13 | def install 14 | template('model.rb.erb', ::File.join('app', directory, "#{file_name}.rb")) 15 | template('model_spec.rb.erb', ::File.join('spec', directory, "#{file_name}_spec.rb")) 16 | return unless options[:datastream] 17 | template('datastream.rb.erb', ::File.join('app', datastream_directory, "#{file_name}_metadata.rb")) 18 | template('datastream_spec.rb.erb', ::File.join('spec', datastream_directory, "#{file_name}_metadata_spec.rb")) 19 | end 20 | 21 | protected 22 | 23 | def directory 24 | options[:directory] 25 | end 26 | 27 | def datastream_directory 28 | options[:datastream_directory] 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/has_subresource.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class HasSubresource < SingularAssociation # :nodoc: 3 | def self.macro 4 | :has_subresource 5 | end 6 | 7 | def self.valid_options(options) 8 | super + [:autocreate, :block] 9 | end 10 | 11 | def self.create_reflection(model, name, scope, options, extension = nil) 12 | options[:class_name] = 'ActiveFedora::File' if options[:class_name].blank? 13 | super(model, name, scope, options, extension) 14 | end 15 | 16 | def self.validate_options(options) 17 | super 18 | return unless options[:class_name] && !options[:class_name].is_a?(String) 19 | raise ArgumentError, ":class_name must be a string for contains '#{name}'" 20 | end 21 | 22 | def self.define_readers(mixin, name) 23 | mixin.send(:define_method, name) do |*params| 24 | association(name).reader(*params).tap do |file| 25 | set_uri = uri.is_a?(RDF::URI) ? uri.value.present? : uri.present? 26 | if set_uri 27 | file_uri = "#{uri}/#{name}" 28 | begin 29 | file.uri = file_uri 30 | rescue ActiveFedora::AlreadyPersistedError 31 | end 32 | end 33 | if file.respond_to?(:exists!) 34 | file.exists! if contains_assertions.include?(file_uri) 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/directly_contains_association.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Associations 3 | class DirectlyContainsAssociation < ContainsAssociation # :nodoc: 4 | def insert_record(record, force = true, validate = true) 5 | container.save! 6 | super 7 | end 8 | 9 | def find_target 10 | query_node = if container_predicate = options[:has_member_relation] 11 | owner 12 | else 13 | container_predicate = ::RDF::Vocab::LDP.contains 14 | container 15 | end 16 | 17 | uris = query_node.resource.query({ predicate: container_predicate }).map { |r| r.object.to_s } 18 | 19 | uris.map { |object_uri| klass.find(klass.uri_to_id(object_uri)) } 20 | end 21 | 22 | def container 23 | @container ||= DirectContainer.find_or_initialize(ActiveFedora::Base.uri_to_id(uri)).tap do |container| 24 | container.parent = @owner 25 | container.has_member_relation = Array(options[:has_member_relation]) 26 | container.is_member_of_relation = Array(options[:is_member_of_relation]) 27 | end 28 | end 29 | 30 | protected 31 | 32 | def initialize_attributes(record) # :nodoc: 33 | record.uri = ActiveFedora::Base.id_to_uri(container.mint_id) 34 | set_inverse_instance(record) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/active_fedora/rdf/field_map_entry.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::RDF 2 | # Transient class that represents a field that we send to solr. 3 | # It might be possible for two properties to share a single field map entry if they use the same solr key. 4 | # @attribute [Symbol] type the data type hint for ActiveFedora::Indexing::FieldMapper 5 | # @attribute [Array] behaviors the indexing hints such as :stored_searchable or :symbol 6 | # @!attribute [w] values the raw values 7 | class FieldMapEntry 8 | attr_accessor :type, :behaviors 9 | attr_writer :values 10 | 11 | def initialize 12 | @behaviors = [] 13 | @values = [] 14 | end 15 | 16 | # Merges any existing values for solr fields with new, incoming values and ensures that resulting values are unique. 17 | # @param [Symbol] type the data type for the field such as :string, :date, :integer 18 | # @param [Array] behaviors FieldMapper's behaviors for indexing such as :stored_searchable, :symbol 19 | # @param [Array] new_values values to append into the existing solr field 20 | def merge!(type, behaviors, new_values) 21 | self.type ||= type 22 | self.behaviors += behaviors 23 | self.behaviors.uniq! 24 | self.values += new_values 25 | end 26 | 27 | # @return [Array] the actual values that get sent to solr 28 | def values 29 | @values.map do |value| 30 | ValueCaster.new(value).value 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/integration/eradicate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | before(:all) do 5 | class ResurrectionModel < ActiveFedora::Base 6 | after_destroy :eradicate 7 | end 8 | end 9 | 10 | after(:all) do 11 | Object.send(:remove_const, :ResurrectionModel) 12 | end 13 | 14 | context "when an object is has already been deleted" do 15 | let(:ghost) do 16 | obj = described_class.create 17 | obj.destroy 18 | obj.id 19 | end 20 | context "in a typical sitation" do 21 | specify "it cannot be reused" do 22 | expect { described_class.create(id: ghost) }.to raise_error(Ldp::Gone) 23 | end 24 | end 25 | specify "remove its tombstone" do 26 | expect(described_class.eradicate(ghost)).to be true 27 | end 28 | end 29 | 30 | context "when an object has just been deleted" do 31 | let(:zombie) do 32 | obj = described_class.create 33 | obj.destroy 34 | return obj 35 | end 36 | specify "remove its tombstone" do 37 | expect(zombie.eradicate).to be true 38 | end 39 | end 40 | 41 | describe "a model with no tombstones" do 42 | let(:lazarus) do 43 | body = ResurrectionModel.create 44 | soul = body.id 45 | body.destroy 46 | return soul 47 | end 48 | it "allows reusing a uri" do 49 | expect(ResurrectionModel.create(id: lazarus)).to be_kind_of(ResurrectionModel) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/unit/default_model_mapper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::DefaultModelMapper do 4 | subject(:mapper) { described_class.new classifier_class: classifier, solr_field: solr_field, predicate: predicate } 5 | let(:classifier) { class_double(ActiveFedora::ModelClassifier) } 6 | let(:classifier_instance) { instance_double(ActiveFedora::ModelClassifier) } 7 | let(:solr_field) { 'solr_field' } 8 | let(:predicate) { 'info:predicate' } 9 | 10 | describe '#classifier' do 11 | context 'with a solr document' do 12 | let(:solr_document) { { 'solr_field' => ['xyz'] } } 13 | 14 | before do 15 | expect(classifier).to receive(:new).with(['xyz']).and_return(classifier_instance) 16 | end 17 | 18 | it 'creates a classifier from the solr field data' do 19 | expect(mapper.classifier(solr_document)).to eq classifier_instance 20 | end 21 | end 22 | 23 | context 'with a resource' do 24 | let(:graph) do 25 | RDF::Graph.new << [:hello, predicate, 'xyz'] 26 | end 27 | 28 | let(:resource) { instance_double(ActiveFedora::LdpResource, graph: graph) } 29 | 30 | before do 31 | expect(classifier).to receive(:new).with(['xyz']).and_return(classifier_instance) 32 | end 33 | 34 | it 'creates a classifier from the resource model predicate' do 35 | expect(mapper.classifier(resource)).to eq classifier_instance 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/active_fedora/with_metadata/default_schema.rb: -------------------------------------------------------------------------------- 1 | # These are the default properties defined on a resource that has WithMetadata 2 | # added to it. This is most commonly used with ActiveFedora::File, when we want 3 | # to add rdf triples to a non-rdf resource and have them persisted. 4 | module ActiveFedora::WithMetadata 5 | class DefaultSchema < ActiveTriples::Schema 6 | def self.legacy_ebucore_vocabulary 7 | @legacy_ebucore_vocabulary ||= Class.new(RDF::StrictVocabulary("http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#")) do 8 | property :filename, 9 | comment: %(The name of the file containing the Resource.).freeze, 10 | domain: "ebucore:Resource".freeze, 11 | label: "File name".freeze, 12 | range: "xsd:string".freeze, 13 | type: "rdf:Property".freeze 14 | end 15 | end 16 | 17 | property :label, predicate: ::RDF::RDFS.label 18 | property :file_name, predicate: legacy_ebucore_vocabulary.filename 19 | property :file_size, predicate: ::RDF::Vocab::EBUCore.fileSize 20 | property :date_created, predicate: ::RDF::Vocab::EBUCore.dateCreated 21 | property :date_modified, predicate: ::RDF::Vocab::EBUCore.dateModified 22 | property :byte_order, predicate: SweetJPLTerms.byteOrder 23 | # This is a server-managed predicate which means Fedora does not let us change it. 24 | property :file_hash, predicate: ::RDF::Vocab::PREMIS.hasMessageDigest, server_managed: true 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/indexers/global_indexer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Indexers::GlobalIndexer do 4 | let(:global_indexer) { described_class.new(index_types) } 5 | let(:index_types) {} 6 | 7 | describe "#new" do 8 | # The global indexer acts as both an indexer factory and an indexer, since 9 | # the property doesn't matter. 10 | it "returns itself" do 11 | expect(global_indexer.new("bla")).to eq global_indexer 12 | end 13 | end 14 | describe "#index" do 15 | let(:index_obj) { instance_double(ActiveFedora::Indexing::Map::IndexObject, as: nil) } 16 | context "with one index type" do 17 | let(:index_types) { :symbol } 18 | it "passes that to index_obj" do 19 | global_indexer.index(index_obj) 20 | 21 | expect(index_obj).to have_received(:as).with(:symbol) 22 | end 23 | end 24 | context "with multiple index types" do 25 | let(:index_types) { [:symbol, :stored_searchable] } 26 | it "passes that to index_obj" do 27 | global_indexer.index(index_obj) 28 | 29 | expect(index_obj).to have_received(:as).with(:symbol, :stored_searchable) 30 | end 31 | end 32 | context "with no index types" do 33 | let(:global_indexer) { described_class.new } 34 | it "does not pass anything to index_obj" do 35 | global_indexer.index(index_obj) 36 | 37 | expect(index_obj).not_to have_received(:as) 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/active_fedora/solr_hit.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class SolrHit < Delegator 3 | def self.for(hit) 4 | return hit if hit.is_a? ActiveFedora::SolrHit 5 | 6 | SolrHit.new(hit) 7 | end 8 | 9 | delegate :models, to: :classifier 10 | 11 | def __getobj__ 12 | @document # return object we are delegating to, required 13 | end 14 | 15 | alias static_config __getobj__ 16 | 17 | def __setobj__(obj) 18 | @document = obj 19 | end 20 | 21 | attr_reader :document 22 | 23 | def initialize(document) 24 | document = document.with_indifferent_access 25 | super 26 | @document = document 27 | end 28 | 29 | def id 30 | document[ActiveFedora.id_field] 31 | end 32 | 33 | def rdf_uri 34 | ::RDF::URI.new(ActiveFedora::Base.id_to_uri(id)) 35 | end 36 | 37 | def model(opts = {}) 38 | best_model_match = classifier.best_model(opts) 39 | ActiveFedora::Base.logger.warn "Could not find a model for #{id}, defaulting to ActiveFedora::Base" if best_model_match == ActiveFedora::Base 40 | best_model_match 41 | end 42 | 43 | def model?(model_to_check) 44 | models.any? do |model| 45 | model_to_check >= model 46 | end 47 | end 48 | 49 | def reify(opts = {}) 50 | model(opts).find(id, cast: true) 51 | end 52 | 53 | private 54 | 55 | def classifier 56 | ActiveFedora.model_mapper.classifier(document) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/association_scope.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Associations 3 | class AssociationScope # :nodoc: 4 | def self.scope(association) 5 | new(association).scope 6 | end 7 | 8 | attr_reader :association 9 | 10 | delegate :klass, :owner, :reflection, :interpolate, to: :association 11 | delegate :chain, :scope_chain, :options, :source_options, :active_record, to: :reflection 12 | 13 | def initialize(association) 14 | @association = association 15 | end 16 | 17 | def scope 18 | scope = klass.unscoped 19 | add_constraints(scope) 20 | end 21 | 22 | private 23 | 24 | def add_constraints(scope) 25 | chain.each_with_index do |reflection, i| 26 | if reflection.macro == :belongs_to 27 | # Create a partial solr query using the ids. We may add additional filters such as class_name later 28 | scope = scope.where(ActiveFedora::SolrQueryBuilder.construct_query_for_ids([owner[reflection.foreign_key]])) 29 | elsif reflection.macro == :has_and_belongs_to_many 30 | else 31 | scope = scope.where(ActiveFedora::SolrQueryBuilder.construct_query_for_rel(association.send(:find_reflection) => owner.id)) 32 | end 33 | 34 | is_first_chain = i.zero? 35 | is_first_chain ? klass : reflection.klass 36 | end 37 | 38 | scope 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/elevate.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/stopwords.txt: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | #----------------------------------------------------------------------- 17 | # a couple of test stopwords to test that the words are really being 18 | # configured from this file: 19 | stopworda 20 | stopwordb 21 | 22 | #Standard english stop words taken from Lucene's StopAnalyzer 23 | a 24 | an 25 | and 26 | are 27 | as 28 | at 29 | be 30 | but 31 | by 32 | for 33 | if 34 | in 35 | into 36 | is 37 | it 38 | no 39 | not 40 | of 41 | on 42 | or 43 | s 44 | such 45 | t 46 | that 47 | the 48 | their 49 | then 50 | there 51 | these 52 | they 53 | this 54 | to 55 | was 56 | will 57 | with 58 | 59 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/config/solr/templates/solr/conf/stopwords_en.txt: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | #----------------------------------------------------------------------- 17 | # a couple of test stopwords to test that the words are really being 18 | # configured from this file: 19 | stopworda 20 | stopwordb 21 | 22 | #Standard english stop words taken from Lucene's StopAnalyzer 23 | a 24 | an 25 | and 26 | are 27 | as 28 | at 29 | be 30 | but 31 | by 32 | for 33 | if 34 | in 35 | into 36 | is 37 | it 38 | no 39 | not 40 | of 41 | on 42 | or 43 | s 44 | such 45 | t 46 | that 47 | the 48 | their 49 | then 50 | there 51 | these 52 | they 53 | this 54 | to 55 | was 56 | will 57 | with 58 | 59 | -------------------------------------------------------------------------------- /lib/active_fedora/initializing_connection.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | class InitializingConnection < Delegator 3 | attr_reader :connection, :root_resource_path 4 | 5 | def initialize(connection, root_resource_path) 6 | super(connection) 7 | @connection = connection 8 | @root_resource_path = root_resource_path 9 | @initialized = false 10 | end 11 | 12 | def __getobj__ 13 | @connection 14 | end 15 | 16 | def __setobj__(connection) 17 | @connection = connection 18 | end 19 | 20 | def head(*) 21 | init_base_path 22 | super 23 | end 24 | 25 | def get(*) 26 | init_base_path 27 | super 28 | end 29 | 30 | def delete(*) 31 | init_base_path 32 | super 33 | end 34 | 35 | def post(*) 36 | init_base_path 37 | super 38 | end 39 | 40 | def put(*) 41 | init_base_path 42 | super 43 | end 44 | 45 | def patch(*) 46 | init_base_path 47 | super 48 | end 49 | 50 | private 51 | 52 | # Call this to create a Container Resource to act as the base path for this connection 53 | def init_base_path 54 | return if @initialized 55 | 56 | connection.head(root_resource_path) 57 | ActiveFedora::Base.logger.info "Attempted to init base path `#{root_resource_path}`, but it already exists" 58 | @initialized = true 59 | false 60 | rescue Ldp::NotFound 61 | @initialized = connection.put(root_resource_path, '').success? 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/active_fedora/indexing/descriptor.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Indexing 3 | class Descriptor 4 | attr_reader :index_type 5 | def initialize(*args) 6 | if args.last.is_a? Hash 7 | opts = args.pop 8 | @converter = opts[:converter] 9 | @type_required = opts[:requires_type] 10 | end 11 | @index_type = args 12 | raise InvalidIndexDescriptor, "Invalid index type passed. It should be an array like [:string, :indexed, :stored, :multivalued]. You provided: `#{@index_type}'" unless index_type.is_a? Array 13 | end 14 | 15 | def name_and_converter(field_name, args = nil) 16 | args ||= {} 17 | field_type = args[:type] 18 | if type_required? 19 | raise ArgumentError, "Must provide a :type argument when index_type is `#{self}' for #{field_name}" unless field_type 20 | end 21 | [field_name.to_s + suffix(field_type), converter(field_type)] 22 | end 23 | 24 | def type_required? 25 | @type_required 26 | end 27 | 28 | def evaluate_suffix(field_type) 29 | Suffix.new(index_type.first.is_a?(Proc) ? index_type.first.call(field_type) : index_type.dup) 30 | end 31 | 32 | protected 33 | 34 | # Suffix can be overridden if you want a different method of grabbing the suffix 35 | def suffix(field_type) 36 | evaluate_suffix(field_type).to_s 37 | end 38 | 39 | def converter(field_type) 40 | @converter&.call(field_type) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/unit/inheritance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | before do 5 | class MyDS < ActiveFedora::File 6 | end 7 | 8 | class MySample < ActiveFedora::File 9 | end 10 | 11 | class MyDeepSample < MySample 12 | end 13 | 14 | class Foo < ActiveFedora::Base 15 | has_subresource 'foostream', class_name: 'MyDS' 16 | has_subresource 'dcstream', class_name: 'MySample' 17 | end 18 | 19 | class Bar < ActiveFedora::Base 20 | has_subresource 'barstream', class_name: 'MyDS' 21 | end 22 | 23 | class Baz < Bar 24 | end 25 | end 26 | 27 | subject(:attached_files) { f.attached_files } 28 | let(:f) { Foo.new } 29 | 30 | it "doesn't overwrite stream specs" do 31 | expect(attached_files.values).to match_array [MyDS, MySample] 32 | end 33 | 34 | context 'base_class' do 35 | it 'shallow < Base' do 36 | expect(Bar.base_class).to eq(Bar) 37 | end 38 | 39 | it 'deep < Base' do 40 | expect(Baz.base_class).to eq(Bar) 41 | end 42 | 43 | it 'shallow < File' do 44 | expect(MySample.base_class).to eq(ActiveFedora::File) 45 | end 46 | 47 | it 'deep < File' do 48 | expect(MyDeepSample.base_class).to eq(ActiveFedora::File) 49 | end 50 | end 51 | 52 | after do 53 | Object.send(:remove_const, :Baz) 54 | Object.send(:remove_const, :Bar) 55 | Object.send(:remove_const, :Foo) 56 | Object.send(:remove_const, :MyDS) 57 | Object.send(:remove_const, :MyDeepSample) 58 | Object.send(:remove_const, :MySample) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/unit/forbidden_attributes_protection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Attributes, ".new" do 4 | before(:all) do 5 | class ProtectedParams < ActiveSupport::HashWithIndifferentAccess 6 | attr_accessor :permitted 7 | alias permitted? permitted 8 | 9 | def initialize(attributes) 10 | super(attributes) 11 | @permitted = false 12 | end 13 | 14 | def permit! 15 | @permitted = true 16 | self 17 | end 18 | 19 | def dup 20 | super.tap do |duplicate| 21 | duplicate.instance_variable_set :@permitted, @permitted 22 | end 23 | end 24 | end 25 | 26 | class Person < ActiveFedora::Base 27 | property :first_name, predicate: ::RDF::Vocab::FOAF.firstName, multiple: false 28 | property :gender, predicate: ::RDF::Vocab::FOAF.gender, multiple: false 29 | end 30 | end 31 | 32 | after(:all) do 33 | Object.send(:remove_const, :ProtectedParams) 34 | Object.send(:remove_const, :Person) 35 | end 36 | 37 | context "forbidden attributes" do 38 | let(:params) { ProtectedParams.new(first_name: 'Guille', gender: 'm') } 39 | it "cannot be used for mass assignment" do 40 | expect { Person.new(params) }.to raise_error ActiveModel::ForbiddenAttributesError 41 | end 42 | end 43 | 44 | context "permitted attributes" do 45 | let(:params) { ProtectedParams.new(first_name: 'Guille', gender: 'm').permit! } 46 | it "can be used for mass assignment" do 47 | expect { Person.new(params) }.not_to raise_error 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/contains_association.rb: -------------------------------------------------------------------------------- 1 | # This is the parent class of BasicContainsAssociation, DirectlyContainsAssociation and IndirectlyContainsAssociation 2 | module ActiveFedora 3 | module Associations 4 | class ContainsAssociation < CollectionAssociation # :nodoc: 5 | def insert_record(record, force = true, validate = true) 6 | if force 7 | record.save! 8 | else 9 | record.save(validate: validate) 10 | end 11 | end 12 | 13 | def reader 14 | @records ||= ContainerProxy.new(self) 15 | end 16 | 17 | def include?(other) 18 | if loaded? 19 | target.include?(other) 20 | elsif container_predicate = options[:has_member_relation] 21 | owner.resource.query({ predicate: container_predicate, object: ::RDF::URI(other.uri) }).present? 22 | else # is_member_of_relation 23 | # This will force a load, so it's slowest and the least preferable option 24 | target.include?(other) 25 | end 26 | end 27 | 28 | protected 29 | 30 | def count_records 31 | load_target.size 32 | end 33 | 34 | def uri 35 | raise "Can't get uri. Owner isn't saved" if @owner.new_record? 36 | "#{@owner.uri}/#{@reflection.name}" 37 | end 38 | 39 | private 40 | 41 | def delete_records(records, method) 42 | if method == :destroy 43 | records.each(&:destroy) 44 | else 45 | records.each(&:delete) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/unit/rspec_matchers/have_predicate_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require 'ostruct' 3 | require "active_fedora/rspec_matchers/have_predicate_matcher" 4 | 5 | describe RSpec::Matchers, ".have_predicate" do 6 | let(:open_struct) { OpenStruct.new(id: id) } 7 | let(:id) { 123 } 8 | let(:object1) { Object.new } 9 | let(:object2) { Object.new } 10 | let(:object3) { Object.new } 11 | let(:predicate) { :predicate } 12 | 13 | it 'matches when relationship is "what we have in Fedora"' do 14 | expect(open_struct.class).to receive(:find).with(id).and_return(open_struct) 15 | expect(open_struct).to receive(:relationships).with(predicate).and_return([object1, object2]) 16 | expect(open_struct).to have_predicate(predicate).with_objects([object1, object2]) 17 | end 18 | 19 | it 'does not match when relationship is different' do 20 | expect(open_struct.class).to receive(:find).with(id).and_return(open_struct) 21 | expect(open_struct).to receive(:relationships).with(predicate).and_return([object1, object3]) 22 | expect { 23 | expect(open_struct).to have_predicate(predicate).with_objects([object1, object2]) 24 | }.to raise_error RSpec::Expectations::ExpectationNotMetError, 25 | /expected #{open_struct.class} ID=#{id} relationship: #{predicate.inspect}/ 26 | end 27 | 28 | it 'requires :with_objects option' do 29 | expect { 30 | expect(open_struct).to have_predicate(predicate) 31 | }.to( 32 | raise_error( 33 | ArgumentError, 34 | "expect(subject).to have_predicate().with_objects()" 35 | ) 36 | ) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/active_fedora/rspec_matchers/have_predicate_matcher.rb: -------------------------------------------------------------------------------- 1 | # RSpec matcher to spec delegations. 2 | 3 | RSpec::Matchers.define :have_predicate do |predicate| 4 | match do |subject| 5 | @predicate = predicate 6 | if @predicate.nil? || !@expected_objects.respond_to?(:count) 7 | raise( 8 | ArgumentError, 9 | "expect(subject).to have_predicate().with_objects()" 10 | ) 11 | end 12 | @subject = subject.class.find(subject.id) 13 | @actual_objects = @subject.relationships(predicate) 14 | 15 | if @expected_objects 16 | actual_count = @actual_objects.count 17 | expected_count = @expected_objects.count 18 | if actual_count != expected_count 19 | raise( 20 | RSpec::Expectations::ExpectationNotMetError, 21 | "#{@subject.class} ID=#{@subject.id} relationship: #{@predicate.inspect} count " 22 | ) 23 | end 24 | intersection = @actual_objects & @expected_objects 25 | 26 | intersection.count == @expected_objects.count 27 | end 28 | end 29 | 30 | chain(:with_objects) { |objects| @expected_objects = objects } 31 | 32 | description do 33 | "#{@subject.class} ID=#{@subject.id} relationship: #{@predicate.inspect} matches Fedora" 34 | end 35 | 36 | failure_message do |_text| 37 | "expected #{@subject.class} ID=#{@subject.id} relationship: #{@predicate.inspect} to match" 38 | end 39 | 40 | failure_message_when_negated do |_text| 41 | "expected #{@subject.class} ID=#{@subject.id} relationship: #{@predicate.inspect} to NOT match" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/unit/reflection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Reflection::AssociationReflection do 4 | describe "#derive_foreign_key" do 5 | subject { instance.send :derive_foreign_key } 6 | let(:name) { 'dummy' } 7 | let(:options) { { inverse_of: :default_permissions } } 8 | let(:active_fedora) { instance_double(ActiveFedora::Base) } 9 | 10 | context "when a has_many" do 11 | let(:instance) { ActiveFedora::Reflection::HasManyReflection.new(name, nil, options, active_fedora) } 12 | 13 | context "and the inverse is a collection association" do 14 | let(:inverse) { instance_double(ActiveFedora::Reflection::HasAndBelongsToManyReflection, collection?: true) } 15 | before { allow(instance).to receive(:inverse_of).and_return(inverse) } 16 | it { is_expected.to eq 'default_permission_ids' } 17 | end 18 | end 19 | end 20 | 21 | describe "#automatic_inverse_of" do 22 | before do 23 | class Dummy < ActiveFedora::Base 24 | belongs_to :foothing, predicate: ::RDF::Vocab::DC.extent 25 | end 26 | end 27 | 28 | after { Object.send(:remove_const, :Dummy) } 29 | subject { instance.send :automatic_inverse_of } 30 | let(:name) { 'dummy' } 31 | let(:options) { { as: 'foothing' } } 32 | let(:active_fedora) { instance_double(ActiveFedora::Base) } 33 | 34 | context "when a has_many" do 35 | let(:instance) { ActiveFedora::Reflection::HasManyReflection.new(name, nil, options, active_fedora) } 36 | 37 | context "and the inverse is a collection association" do 38 | it { is_expected.to eq :foothing } 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/active_fedora/orders/target_proxy.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Orders 3 | class TargetProxy 4 | attr_reader :association 5 | delegate :+, to: :to_a 6 | def initialize(association) 7 | @association = association 8 | end 9 | 10 | def <<(obj) 11 | association.append_target(obj) 12 | self 13 | end 14 | 15 | def concat(objs) 16 | objs.each do |obj| 17 | self.<<(obj) 18 | end 19 | self 20 | end 21 | 22 | def insert_at(loc, record) 23 | association.insert_target_at(loc, record) 24 | self 25 | end 26 | 27 | def ids 28 | association.reader.map(&:target_id) 29 | end 30 | 31 | # Deletes the element at the specified index, returning that element, or nil if 32 | # the index is out of range. 33 | def delete_at(loc) 34 | result = association.delete_at(loc) 35 | result&.target 36 | end 37 | 38 | # Deletes all items from self that are equal to obj. 39 | # @param obj the object to remove from the list 40 | # @return the last deleted item, or nil if no matching item is found 41 | def delete(obj) 42 | association.delete_target(obj) 43 | end 44 | 45 | def clear 46 | association.delete_at(0) while to_ary.present? 47 | end 48 | 49 | def to_ary 50 | association.reader.map(&:target).dup 51 | end 52 | alias to_a to_ary 53 | 54 | def ==(other) 55 | case other 56 | when TargetProxy 57 | super 58 | when Array 59 | to_a == other 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/unit/query_result_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::QueryResultBuilder do 4 | describe "reify solr results" do 5 | before(:all) do 6 | class AudioRecord < ActiveFedora::Base 7 | attr_accessor :id 8 | def self.connection_for_id(_id); end 9 | end 10 | @sample_solr_hits = [{ "id" => "my:_ID1_", ActiveFedora.index_field_mapper.solr_name("has_model", :symbol) => ["AudioRecord"] }, 11 | { "id" => "my:_ID2_", ActiveFedora.index_field_mapper.solr_name("has_model", :symbol) => ["AudioRecord"] }, 12 | { "id" => "my:_ID3_", ActiveFedora.index_field_mapper.solr_name("has_model", :symbol) => ["AudioRecord"] }] 13 | end 14 | describe ".reify_solr_results" do 15 | it "uses AudioRecord.find to instantiate objects" do 16 | expect(AudioRecord).to receive(:find).with("my:_ID1_", cast: true) 17 | expect(AudioRecord).to receive(:find).with("my:_ID2_", cast: true) 18 | expect(AudioRecord).to receive(:find).with("my:_ID3_", cast: true) 19 | described_class.reify_solr_results(@sample_solr_hits) 20 | end 21 | end 22 | # rubocop:disable Lint/Void 23 | describe ".lazy_reify_solr_results" do 24 | it "lazilies reify solr results" do 25 | expect(AudioRecord).to receive(:find).with("my:_ID1_", cast: true) 26 | expect(AudioRecord).to receive(:find).with("my:_ID2_", cast: true) 27 | expect(AudioRecord).to receive(:find).with("my:_ID3_", cast: true) 28 | described_class.lazy_reify_solr_results(@sample_solr_hits).each { |r| r } 29 | end 30 | end 31 | # rubocop:enable Lint/Void 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/generators/active_fedora/model/templates/model.rb.erb: -------------------------------------------------------------------------------- 1 | # Generated via 2 | # `rails generate active_fedora:model <%= class_name %>` 3 | class <%= class_name %> < ActiveFedora::Base 4 | <% if options['datastream'] %> 5 | has_subresource :descMetadata, class_name: "<%= options['datastream'] %>" 6 | <% else %> 7 | # Define some properties to store: 8 | # 9 | property :title, predicate: ::RDF::Vocab::DC.title do |index| 10 | index.as :stored_searchable, :facetable 11 | end 12 | property :creator, predicate: ::RDF::Vocab::DC.creator do |index| 13 | index.as :stored_searchable, :facetable 14 | end 15 | property :contributor, predicate: ::RDF::Vocab::DC.contributor do |index| 16 | index.as :stored_searchable, :facetable 17 | end 18 | property :description, predicate: ::RDF::Vocab::DC.description do |index| 19 | index.as :stored_searchable 20 | end 21 | <%- end -%> 22 | <% if options['has_subresource'] %> 23 | has_subresource :<%= options['has_subresource'] %>" 24 | <% else %> 25 | # Uncomment the following lines to add an #attachment method that is a file 26 | # 27 | # has_subresource "attachment" 28 | <% end %> 29 | # 30 | # If you need to add additional attributes to the SOLR document, extend the default indexer: 31 | # 32 | # def indexer 33 | # MyApp::IndexingService 34 | # end 35 | # 36 | # This can go into app/services/my_app/indexing_service.rb 37 | # module MyApp 38 | # class IndexingService < ActiveFedora::IndexingService 39 | # def generate_solr_document 40 | # super.tap do |solr_doc| 41 | # solr_doc["my_attribute_s"] = object.my_attribute 42 | # end 43 | # end 44 | # end 45 | # end 46 | end 47 | -------------------------------------------------------------------------------- /spec/unit/aggregation/ordered_reader_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Aggregation::OrderedReader do 4 | subject(:ordered_reader) { described_class.new(root) } 5 | let(:root) { instance_double(ActiveFedora::Aggregation::ListSource) } 6 | 7 | describe "#each" do 8 | it "iterates a linked list" do 9 | head = build_node 10 | tail = build_node(prev_node: head) 11 | allow(head).to receive(:next).and_return(tail) 12 | allow(root).to receive(:head).and_return(head) 13 | expect(ordered_reader.to_a).to eq [head, tail] 14 | end 15 | it "only goes as deep as necessary" do 16 | head = build_node 17 | tail = build_node(prev_node: head) 18 | allow(head).to receive(:next).and_return(tail) 19 | allow(root).to receive(:head).and_return(head) 20 | expect(ordered_reader.first).to eq head 21 | expect(head).not_to have_received(:next) 22 | end 23 | context "when the prev is wrong" do 24 | it "fixes it up" do 25 | head = build_node 26 | bad_node = build_node 27 | tail = build_node(prev_node: bad_node) 28 | allow(head).to receive(:next).and_return(tail) 29 | allow(root).to receive(:head).and_return(head) 30 | allow(tail).to receive(:prev=) 31 | expect(ordered_reader.to_a).to eq [head, tail] 32 | expect(tail).to have_received(:prev=).with(head) 33 | end 34 | end 35 | end 36 | 37 | def build_node(prev_node: nil, next_node: nil) 38 | node = instance_double(ActiveFedora::Orders::ListNode) 39 | allow(node).to receive(:next).and_return(next_node) 40 | allow(node).to receive(:prev).and_return(prev_node) 41 | node 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/unit/rspec_matchers/belong_to_associated_active_fedora_object_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require 'ostruct' 3 | require "active_fedora/rspec_matchers/belong_to_associated_active_fedora_object_matcher" 4 | 5 | describe RSpec::Matchers, ".belong_to_associated_active_fedora_object" do 6 | let(:open_struct) { OpenStruct.new(id: id) } 7 | let(:id) { 123 } 8 | let(:object1) { Object.new } 9 | let(:object2) { Object.new } 10 | let(:association) { :association } 11 | 12 | it 'matches when association is properly stored in fedora' do 13 | expect(open_struct.class).to receive(:find).with(id).and_return(open_struct) 14 | expect(open_struct).to receive(association).and_return(object1) 15 | expect(open_struct).to belong_to_associated_active_fedora_object(association).with_object(object1) 16 | end 17 | 18 | it 'does not match when association is different' do 19 | expect(open_struct.class).to receive(:find).with(id).and_return(open_struct) 20 | expect(open_struct).to receive(association).and_return(object1) 21 | expect { 22 | expect(open_struct).to belong_to_associated_active_fedora_object(association).with_object(object2) 23 | }.to raise_error RSpec::Expectations::ExpectationNotMetError, 24 | /expected #{open_struct.class} ID=#{id} association: #{association.inspect}/ 25 | end 26 | 27 | it 'requires :with_object option' do 28 | expect { 29 | expect(open_struct).to belong_to_associated_active_fedora_object(association) 30 | }.to( 31 | raise_error( 32 | ArgumentError, 33 | "expect(subject).to belong_to_associated_active_fedora_object().with_object()" 34 | ) 35 | ) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/active_fedora/indexing/inserter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe ActiveFedora::Indexing::Inserter do 4 | let(:solr_doc) { {} } 5 | 6 | it "handles many field types" do 7 | described_class.create_and_insert_terms('my_name', 'value', [:displayable, :searchable, :sortable], solr_doc) 8 | expect(solr_doc).to eq('my_name_ssm' => ['value'], 'my_name_si' => 'value', 'my_name_teim' => ['value']) 9 | end 10 | 11 | it "handles dates that are searchable" do 12 | described_class.create_and_insert_terms('my_name', Date.parse('2013-01-10'), [:stored_searchable], solr_doc) 13 | expect(solr_doc).to eq('my_name_dtsim' => ['2013-01-10T00:00:00Z']) 14 | end 15 | 16 | it "handles dates that are stored_sortable" do 17 | described_class.create_and_insert_terms('my_name', Date.parse('2013-01-10'), [:stored_sortable], solr_doc) 18 | expect(solr_doc).to eq('my_name_dtsi' => '2013-01-10T00:00:00Z') 19 | end 20 | 21 | it "handles dates that are displayable" do 22 | described_class.create_and_insert_terms('my_name', Date.parse('2013-01-10'), [:displayable], solr_doc) 23 | expect(solr_doc).to eq('my_name_ssm' => ['2013-01-10']) 24 | end 25 | 26 | it "handles dates that are sortable" do 27 | described_class.create_and_insert_terms('my_name', Date.parse('2013-01-10'), [:sortable], solr_doc) 28 | expect(solr_doc).to eq('my_name_dti' => '2013-01-10T00:00:00Z') 29 | end 30 | 31 | it "handles floating point integers" do 32 | described_class.create_and_insert_terms('my_number', (6.022140857 * 10**23).to_f, [:displayable, :searchable], solr_doc) 33 | expect(solr_doc).to eq('my_number_ssm' => ['6.0221408569999995e+23'], 'my_number_fim' => ['6.0221408569999995e+23']) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/active_fedora/base.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/descendants_tracker' 2 | require 'active_fedora/errors' 3 | require 'active_fedora/log_subscriber' 4 | 5 | module ActiveFedora 6 | # This class ties together many of the lower-level modules, and 7 | # implements something akin to an ActiveRecord-alike interface to 8 | # fedora. If you want to represent a fedora object in the ruby 9 | # space, this is the class you want to extend. 10 | # 11 | # =The Basics 12 | # class Oralhistory < ActiveFedora::Base 13 | # property :creator, predicate: RDF::Vocab::DC.creator 14 | # end 15 | # 16 | # The above example creates a Fedora object with a property named "creator" 17 | # 18 | # Attached files defined with +has_subresource+ and iis accessed via the +attached_files+ member hash. 19 | # 20 | class Base 21 | extend ActiveModel::Naming 22 | extend ActiveSupport::DescendantsTracker 23 | extend LdpCache::ClassMethods 24 | 25 | include AttributeAssignment 26 | include Core 27 | include Identifiable 28 | include Inheritance 29 | include Persistence 30 | include Indexing 31 | include Scoping 32 | include ActiveModel::Conversion 33 | include Callbacks 34 | include Validations 35 | extend Querying 36 | include Associations 37 | include AutosaveAssociation 38 | include NestedAttributes 39 | include Serialization 40 | 41 | include AttachedFiles 42 | include FedoraAttributes 43 | include Reflection 44 | include AttributeMethods 45 | include Attributes 46 | include Versionable 47 | include LoadableFromJson 48 | include Schema 49 | include Aggregation::BaseExtension 50 | end 51 | 52 | ActiveSupport.run_load_hooks(:active_fedora, Base) 53 | end 54 | -------------------------------------------------------------------------------- /spec/unit/files_hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::FilesHash do 4 | before do 5 | class FilesContainer; end 6 | allow(FilesContainer).to receive(:child_resource_reflections).and_return(file: reflection) 7 | allow(container).to receive(:association).with(:file).and_return(association) 8 | allow(container).to receive(:undeclared_files).and_return([]) 9 | end 10 | 11 | after { Object.send(:remove_const, :FilesContainer) } 12 | 13 | subject(:file_hash) { described_class.new(container) } 14 | let(:reflection) { instance_double(ActiveFedora::Reflection::MacroReflection) } 15 | let(:association) { instance_double(ActiveFedora::Associations::SingularAssociation, reader: object) } 16 | let(:object) { double('object') } 17 | let(:container) { FilesContainer.new } 18 | 19 | describe "#key?" do 20 | context 'when the key is present' do 21 | it "is true" do 22 | expect(file_hash.key?(:file)).to be true 23 | end 24 | it "returns true if a string is passed" do 25 | expect(file_hash.key?('file')).to be true 26 | end 27 | end 28 | 29 | context 'when the key is not present' do 30 | it "is false" do 31 | expect(file_hash.key?(:foo)).to be false 32 | end 33 | end 34 | end 35 | 36 | describe "#[]" do 37 | context 'when the key is present' do 38 | it "returns the object" do 39 | expect(file_hash[:file]).to eq object 40 | end 41 | it "returns the object if a string is passed" do 42 | expect(file_hash['file']).to eq object 43 | end 44 | end 45 | 46 | context 'when the key is not present' do 47 | it "is nil" do 48 | expect(file_hash[:foo]).to be_nil 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/integration/relation_delegation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Base do 4 | before(:all) do 5 | class TestClass < ActiveFedora::Base 6 | property :foo, predicate: ::RDF::URI('http://example.com/foo') 7 | property :bar, predicate: ::RDF::URI('http://example.com/bar') do |index| 8 | index.as :stored_searchable 9 | end 10 | 11 | def to_solr(doc = {}) 12 | doc = super 13 | doc[ActiveFedora.index_field_mapper.solr_name('foo', :sortable)] = doc[ActiveFedora.index_field_mapper.solr_name('foo', type: :string)] 14 | doc 15 | end 16 | end 17 | end 18 | 19 | after(:all) do 20 | Object.send(:remove_const, :TestClass) 21 | end 22 | 23 | describe "with multiple objects" do 24 | let!(:instance1) { TestClass.create!(foo: ['Beta'], bar: ['Chips']) } 25 | let!(:instance2) { TestClass.create!(foo: ['Alpha'], bar: ['Peanuts']) } 26 | let!(:instance3) { TestClass.create!(foo: ['Sigma'], bar: ['Peanuts']) } 27 | 28 | subject(:peanuts) { TestClass.where(bar: 'Peanuts') } 29 | 30 | it "maps" do 31 | expect(peanuts.map(&:id)).to contain_exactly instance2.id, instance3.id 32 | end 33 | 34 | it "collects" do 35 | expect(peanuts.collect(&:id)).to contain_exactly instance2.id, instance3.id 36 | end 37 | 38 | it "has each" do 39 | expect(peanuts.each.to_a.length).to eq 2 40 | end 41 | 42 | it "has all?" do 43 | expect(peanuts.all? { |t| t.foo == ['Alpha'] }).to be false 44 | expect(peanuts.all? { |t| t.bar == ['Peanuts'] }).to be true 45 | end 46 | 47 | it "has include?" do 48 | expect(peanuts.include?(instance1)).to be false 49 | expect(peanuts.include?(instance2)).to be true 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/active_fedora/rspec_matchers/have_many_associated_active_fedora_objects_matcher.rb: -------------------------------------------------------------------------------- 1 | # RSpec matcher to spec delegations. 2 | 3 | RSpec::Matchers.define :have_many_associated_active_fedora_objects do |association_name| 4 | match do |subject| 5 | @association_name = association_name 6 | if @association_name.nil? || !@expected_objects.respond_to?(:count) 7 | raise( 8 | ArgumentError, 9 | "expect(subject).to have_many_associated_active_fedora_objects().with_objects()" 10 | ) 11 | end 12 | 13 | @subject = subject.class.find(subject.id) 14 | @actual_objects = @subject.send(@association_name) 15 | 16 | if @expected_objects 17 | actual_count = @actual_objects.count 18 | expected_count = @expected_objects.count 19 | if actual_count != expected_count 20 | raise( 21 | RSpec::Expectations::ExpectationNotMetError, 22 | "#{@subject.class} ID=#{@subject.id} relationship: #{@association_name.inspect} count " 23 | ) 24 | end 25 | intersection = @actual_objects & @expected_objects 26 | intersection.count == @expected_objects.count 27 | end 28 | end 29 | 30 | chain(:with_objects) { |objects| @expected_objects = objects } 31 | 32 | description do 33 | "#{@subject.class} ID=#{@subject.id} association: #{@association_name.inspect} matches ActiveFedora" 34 | end 35 | 36 | failure_message do |_text| 37 | "expected #{@subject.class} ID=#{@subject.id} association: #{@association_name.inspect} to match" 38 | end 39 | 40 | failure_message_when_negated do |_text| 41 | "expected #{@subject.class} ID=#{@subject.id} association: #{@association_name.inspect} to NOT match" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/active_fedora/indexing/inserter.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Indexing 3 | # Utilities for adding fields to index documents 4 | class Inserter 5 | # @param [String] field_name_base the field name 6 | # @param [String] value the value to insert into the index 7 | # @param [Array] index_as the index type suffixes 8 | # @param [Hash] solr_doc the index doc to add to 9 | # @example: 10 | # solr_doc = {} 11 | # create_and_insert_terms('title', 'War and Peace', [:displayable, :searchable], solr_doc) 12 | # solr_doc 13 | # # => {"title_ssm"=>["War and Peace"], "title_teim"=>["War and Peace"]} 14 | def self.create_and_insert_terms(field_name_base, value, index_as, solr_doc) 15 | index_as.each do |indexer| 16 | insert_field(solr_doc, field_name_base, value, indexer) 17 | end 18 | end 19 | 20 | # @params [Hash] doc the hash to insert the value into 21 | # @params [String] name the name of the field (without the suffix) 22 | # @params [String,Date,Array] value the value (or array of values) to be inserted 23 | # @params [Array,Hash] indexer_args the arguments that find the indexer 24 | # @returns [Hash] doc the document that was provided with the new field inserted 25 | def self.insert_field(doc, name, value, *indexer_args) 26 | # adding defaults indexer 27 | indexer_args = [:stored_searchable] if indexer_args.empty? 28 | ActiveFedora.index_field_mapper.solr_names_and_values(name, value, indexer_args).each do |k, v| 29 | doc[k] ||= [] 30 | if v.is_a? Array 31 | doc[k] += v 32 | else 33 | doc[k] = v 34 | end 35 | end 36 | doc 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/active_fedora/versionable.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Versionable 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | class_attribute :versionable 7 | end 8 | 9 | # Returns an array of ActiveFedora::VersionsGraph::ResourceVersion objects. 10 | def versions(reload = false) 11 | response = versions_request 12 | 13 | return ActiveFedora::VersionsGraph.new unless response 14 | if reload 15 | @versions = ActiveFedora::VersionsGraph.new << versions_request.reader 16 | else 17 | @versions ||= ActiveFedora::VersionsGraph.new << versions_request.reader 18 | end 19 | end 20 | 21 | def create_version 22 | resp = ActiveFedora.fedora.connection.post(versions_uri, nil) 23 | @versions = nil 24 | resp.success? 25 | end 26 | 27 | # Queries Fedora to figure out if there are versions for the resource. 28 | def has_versions? 29 | resp = ActiveFedora.fedora.connection.get(versions_uri) 30 | graph = ::RDF::Graph.new << resp.reader 31 | graph.query({ predicate: ::RDF::Vocab::LDP.contains }).present? 32 | rescue Ldp::NotFound 33 | false 34 | end 35 | 36 | private 37 | 38 | def versions_request 39 | ActiveFedora.fedora.connection.get(versions_uri) 40 | rescue Ldp::NotFound 41 | false 42 | end 43 | 44 | def versions_uri 45 | uri + '/fcr:versions' 46 | end 47 | 48 | def status_message(response) 49 | "Unexpected return value #{response.status} when retrieving datastream content at #{uri}\n\t#{response.body}" 50 | end 51 | 52 | def bad_headers(response) 53 | "Unknown response format. Got '#{response.headers['content-type']}', but was expecting 'text/turtle'" 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/active_fedora/relation/spawn_methods.rb: -------------------------------------------------------------------------------- 1 | require 'active_fedora/relation/merger' 2 | 3 | module ActiveFedora 4 | module SpawnMethods 5 | # This is overridden by Associations::CollectionProxy 6 | def spawn # :nodoc: 7 | clone 8 | end 9 | 10 | # Merges in the conditions from other, if other is an ActiveRecord::Relation. 11 | # Returns an array representing the intersection of the resulting records with other, if other is an array. 12 | # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) ) 13 | # # Performs a single join query with both where conditions. 14 | # 15 | # recent_posts = Post.order('created_at DESC').first(5) 16 | # Post.where(published: true).merge(recent_posts) 17 | # # Returns the intersection of all published posts with the 5 most recently created posts. 18 | # # (This is just an example. You'd probably want to do this with a single query!) 19 | # 20 | # Procs will be evaluated by merge: 21 | # 22 | # Post.where(published: true).merge(-> { joins(:comments) }) 23 | # # => Post.where(published: true).joins(:comments) 24 | # 25 | # This is mainly intended for sharing common conditions between multiple associations. 26 | def merge(other) 27 | if other.is_a?(Array) 28 | to_a & other 29 | elsif other 30 | spawn.merge!(other) 31 | else 32 | self 33 | end 34 | end 35 | 36 | def merge!(other) # :nodoc: 37 | if !other.is_a?(Relation) && other.respond_to?(:to_proc) 38 | instance_exec(&other) 39 | else 40 | klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger 41 | klass.new(self, other).merge 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/unit/rspec_matchers/have_many_associated_active_fedora_objects_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require 'ostruct' 3 | require "active_fedora/rspec_matchers/have_many_associated_active_fedora_objects_matcher" 4 | 5 | describe RSpec::Matchers, ".have_many_associated_active_fedora_objects" do 6 | let(:open_struct) { OpenStruct.new(id: id) } 7 | let(:id) { 123 } 8 | let(:object1) { Object.new } 9 | let(:object2) { Object.new } 10 | let(:object3) { Object.new } 11 | let(:association) { :association } 12 | 13 | it 'matches when association is properly stored in fedora' do 14 | expect(open_struct.class).to receive(:find).with(id).and_return(open_struct) 15 | expect(open_struct).to receive(association).and_return([object1, object2]) 16 | expect(open_struct).to have_many_associated_active_fedora_objects(association).with_objects([object1, object2]) 17 | end 18 | 19 | it 'does not match when association is different' do 20 | expect(open_struct.class).to receive(:find).with(id).and_return(open_struct) 21 | expect(open_struct).to receive(association).and_return([object1, object3]) 22 | expect { 23 | expect(open_struct).to have_many_associated_active_fedora_objects(association).with_objects([object1, object2]) 24 | }.to raise_error RSpec::Expectations::ExpectationNotMetError, 25 | /expected #{open_struct.class} ID=#{id} association: #{association.inspect}/ 26 | end 27 | 28 | it 'requires :with_objects option' do 29 | expect { 30 | expect(open_struct).to have_many_associated_active_fedora_objects(association) 31 | }.to( 32 | raise_error( 33 | ArgumentError, 34 | "expect(subject).to have_many_associated_active_fedora_objects().with_objects()" 35 | ) 36 | ) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/scoping_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Scoping::Default do 4 | describe "when default_scope is overridden" do 5 | before do 6 | class Book < ActiveFedora::Base 7 | property :published, predicate: ::RDF::Vocab::EBUCore.pubStatus do |index| 8 | index.as :symbol 9 | end 10 | 11 | def self.default_scope 12 | where published_ssim: 'true' 13 | end 14 | end 15 | 16 | Book.destroy_all 17 | Book.create!(published: [true]) 18 | Book.create!(published: [true]) 19 | Book.create!(published: [false]) 20 | end 21 | 22 | after do 23 | Object.send(:remove_const, :Book) 24 | end 25 | 26 | it "returns only the scoped records" do 27 | expect(Book.all.size).to eq 2 28 | end 29 | 30 | it "returns all the records" do 31 | Book.unscoped do 32 | expect(Book.all.size).to eq 3 33 | end 34 | end 35 | end 36 | 37 | describe "when default_scope is called" do 38 | before do 39 | class Book < ActiveFedora::Base 40 | property :published, predicate: ::RDF::Vocab::EBUCore.pubStatus do |index| 41 | index.as :symbol 42 | end 43 | 44 | default_scope -> { where published_ssim: 'true' } 45 | end 46 | 47 | Book.destroy_all 48 | Book.create!(published: [true]) 49 | Book.create!(published: [true]) 50 | Book.create!(published: [false]) 51 | end 52 | 53 | after do 54 | Object.send(:remove_const, :Book) 55 | end 56 | 57 | it "returns only the scoped records" do 58 | expect(Book.all.size).to eq 2 59 | end 60 | 61 | it "returns all the records" do 62 | Book.unscoped do 63 | expect(Book.all.size).to eq 3 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/unit/solr_hit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::SolrHit do 4 | before(:all) do 5 | class AudioRecord < ActiveFedora::Base 6 | attr_accessor :id 7 | def self.connection_for_id(_id); end 8 | end 9 | end 10 | 11 | subject(:solr_hit) { described_class.new "id" => "my:_ID1_", ActiveFedora.index_field_mapper.solr_name("has_model", :symbol) => ["AudioRecord"] } 12 | 13 | describe "#reify" do 14 | it "uses .find to instantiate objects" do 15 | expect(AudioRecord).to receive(:find).with("my:_ID1_", cast: true) 16 | solr_hit.reify 17 | end 18 | end 19 | 20 | describe "#id" do 21 | it "extracts the id from the solr hit" do 22 | expect(solr_hit.id).to eq "my:_ID1_" 23 | end 24 | end 25 | 26 | describe "#rdf_uri" do 27 | it "provides an RDF URI for the solr hit" do 28 | expect(solr_hit.rdf_uri).to eq ::RDF::URI.new(ActiveFedora::Base.id_to_uri("my:_ID1_")) 29 | end 30 | end 31 | 32 | describe "#model" do 33 | it "selects the appropriate model for the solr result" do 34 | expect(solr_hit.model).to eq AudioRecord 35 | end 36 | end 37 | 38 | describe "#models" do 39 | it "provides all the relevant models for the solr result" do 40 | expect(solr_hit.models).to match_array [AudioRecord] 41 | end 42 | end 43 | 44 | describe "#model?" do 45 | it 'is true if the object has the given model' do 46 | expect(solr_hit.model?(AudioRecord)).to eq true 47 | end 48 | 49 | it 'is true if the object has an ancestor of the given model' do 50 | expect(solr_hit.model?(ActiveFedora::Base)).to eq true 51 | end 52 | 53 | it 'is false if the given model is not in the ancestor tree for the models' do 54 | expect(solr_hit.model?(String)).to eq false 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/unit/with_metadata/default_metadata_class_factory_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::WithMetadata::DefaultMetadataClassFactory do 4 | let(:parent) { class_double(ActiveFedora::File) } 5 | let(:object) { described_class.new } 6 | 7 | describe "default class attributes" do 8 | its(:metadata_base_class) { is_expected.to eq(ActiveFedora::WithMetadata::MetadataNode) } 9 | its(:file_metadata_schemas) { is_expected.to eq([ActiveFedora::WithMetadata::DefaultSchema]) } 10 | its(:file_metadata_strategy) { is_expected.to eq(ActiveFedora::WithMetadata::DefaultStrategy) } 11 | end 12 | 13 | describe "::build" do 14 | it "sets MetadataNode to the default schema using the default strategy" do 15 | expect(parent).to receive(:const_set) 16 | expect(parent).to receive(:delegate).with(:label, :label=, :label_changed?, to: :metadata_node) 17 | expect(parent).to receive(:delegate).with(:file_name, :file_name=, :file_name_changed?, to: :metadata_node) 18 | expect(parent).to receive(:delegate).with(:file_size, :file_size=, :file_size_changed?, to: :metadata_node) 19 | expect(parent).to receive(:delegate).with(:date_created, :date_created=, :date_created_changed?, to: :metadata_node) 20 | expect(parent).to receive(:delegate).with(:date_modified, 21 | :date_modified=, 22 | :date_modified_changed?, 23 | to: :metadata_node) 24 | expect(parent).to receive(:delegate).with(:byte_order, :byte_order=, :byte_order_changed?, to: :metadata_node) 25 | expect(parent).to receive(:delegate).with(:file_hash, :file_hash=, :file_hash_changed?, to: :metadata_node) 26 | object.class.build(parent) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/belongs_to_association.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Associations 3 | class BelongsToAssociation < SingularAssociation # :nodoc: 4 | def handle_dependency 5 | target.send(options[:dependent]) if load_target 6 | end 7 | 8 | def replace(record) 9 | if record 10 | raise_on_type_mismatch!(record) 11 | update_counters_on_replace(record) 12 | replace_keys(record) 13 | set_inverse_instance(record) 14 | @updated = true 15 | else 16 | decrement_counters 17 | remove_keys 18 | end 19 | 20 | self.target = record 21 | end 22 | 23 | def reset 24 | super 25 | @updated = false 26 | end 27 | 28 | def updated? 29 | @updated 30 | end 31 | 32 | def decrement_counters # :nodoc: 33 | # noop 34 | end 35 | 36 | def increment_counters # :nodoc: 37 | # noop 38 | end 39 | 40 | private 41 | 42 | def find_target? 43 | !loaded? && foreign_key_present? && klass 44 | end 45 | 46 | def update_counters_on_replace(_record) 47 | # noop 48 | end 49 | 50 | def replace_keys(record) 51 | owner[reflection.foreign_key] = record.id 52 | end 53 | 54 | def remove_keys 55 | owner[reflection.foreign_key] = nil 56 | end 57 | 58 | def foreign_key_present? 59 | owner[reflection.foreign_key] 60 | end 61 | 62 | # belongs_to is not invertible (unless we implement has_one, then make an exception here) 63 | def invertible_for?(_) 64 | false 65 | end 66 | 67 | def stale_state 68 | owner[reflection.foreign_key] 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/active_fedora/identifiable.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Identifiable 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | ## 7 | # :singleton-method 8 | # 9 | # Accepts a proc that takes an id and transforms it to a URI 10 | mattr_accessor :translate_id_to_uri, default: Core::FedoraIdTranslator 11 | 12 | ## 13 | # :singleton-method 14 | # 15 | # Accepts a proc that takes a uri and transforms it to an id 16 | mattr_accessor :translate_uri_to_id, default: Core::FedoraUriTranslator 17 | end 18 | 19 | def id 20 | if uri.is_a?(::RDF::URI) && uri.value.blank? 21 | nil 22 | elsif uri.present? 23 | self.class.uri_to_id(URI.parse(uri)) 24 | end 25 | end 26 | 27 | def id=(id) 28 | raise "ID has already been set to #{self.id}" if self.id 29 | @ldp_source = build_ldp_resource(id.to_s) 30 | end 31 | 32 | # @return [RDF::URI] the uri for this resource 33 | def uri 34 | @ldp_source.subject_uri 35 | end 36 | 37 | module ClassMethods 38 | ## 39 | # Transforms an id into a uri 40 | # if translate_id_to_uri is set it uses that proc, otherwise just the default 41 | def id_to_uri(id) 42 | translate_id_to_uri.call(id) 43 | end 44 | 45 | ## 46 | # Transforms a uri into an id 47 | # if translate_uri_to_id is set it uses that proc, otherwise just the default 48 | def uri_to_id(uri) 49 | translate_uri_to_id.call(uri) 50 | end 51 | 52 | ## 53 | # Provides the common interface for ActiveTriples::Identifiable 54 | def from_uri(uri, _) 55 | find(uri_to_id(uri)) 56 | rescue ActiveFedora::ObjectNotFoundError, Ldp::Gone 57 | ActiveTriples::Resource.new(uri) 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/integration/scoping_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActiveFedora::Scoping::Named do 4 | before(:all) do 5 | class TestClass < ActiveFedora::Base; end 6 | class OtherClass < ActiveFedora::Base; end 7 | end 8 | after(:all) do 9 | Object.send(:remove_const, :TestClass) 10 | Object.send(:remove_const, :OtherClass) 11 | end 12 | 13 | let!(:test_instance) { TestClass.create! } 14 | after { test_instance.delete } 15 | 16 | describe "#all" do 17 | it "returns an array of instances of the calling Class" do 18 | result = TestClass.all.to_a 19 | expect(result).to be_instance_of(Array) 20 | # this test is meaningless if the array length is zero 21 | expect(result).to_not be_empty 22 | result.each do |obj| 23 | expect(obj.class).to eq TestClass 24 | end 25 | end 26 | end 27 | 28 | describe '#find' do 29 | describe "a valid id without cast" do 30 | subject { ActiveFedora::Base.find(test_instance.id) } 31 | it { is_expected.to be_instance_of TestClass } 32 | end 33 | describe "a valid id with cast of false" do 34 | subject { ActiveFedora::Base.find(test_instance.id, cast: false) } 35 | it { is_expected.to be_instance_of ActiveFedora::Base } 36 | end 37 | describe "a valid id without cast on a model extending Base" do 38 | subject { TestClass.find(test_instance.id) } 39 | it { is_expected.to be_instance_of TestClass } 40 | end 41 | it "a valid id on an incompatible class raises ModelMismatch" do 42 | expect { OtherClass.find(test_instance.id) }.to raise_error(ActiveFedora::ModelMismatch) 43 | end 44 | it "invalid id raises ObjectNotFoundError" do 45 | expect { TestClass.find('some_unused_identifier') }.to raise_error(ActiveFedora::ObjectNotFoundError) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/active_fedora/attribute_methods/write.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module AttributeMethods 3 | module Write 4 | module ClassMethods 5 | protected 6 | 7 | def define_method_attribute=(canonical_name, owner: nil, as: canonical_name) 8 | ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( 9 | owner, canonical_name, writer: true 10 | ) do |temp_method_name, attr_name_expr| 11 | # rubocop:disable Style/LineEndConcatenation 12 | if ActiveModel.version >= Gem::Version.new('7.0.0') 13 | owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_fedora) do |batch| 14 | batch << 15 | "def #{temp_method_name}(value)" << 16 | " write_attribute(#{attr_name_expr}, value)" << 17 | "end" 18 | end 19 | else 20 | owner << 21 | "def #{temp_method_name}(value)" << 22 | " write_attribute(#{attr_name_expr}, value)" << 23 | "end" 24 | end 25 | # rubocop:enable Style/LineEndConcatenation 26 | end 27 | end 28 | end 29 | 30 | extend ActiveSupport::Concern 31 | 32 | included do 33 | attribute_method_suffix "=" 34 | end 35 | 36 | def write_attribute(attribute_name, value) 37 | raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attribute_name}'" unless self.class.properties.key?(attribute_name) 38 | 39 | attributes[attribute_name] = value 40 | end 41 | 42 | private 43 | 44 | def attribute=(attribute_name, value) 45 | write_attribute(attribute_name, value) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/builder/aggregation.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora::Associations::Builder 2 | class Aggregation < ActiveFedora::Associations::Builder::Association 3 | def self.valid_options(_options) 4 | [:through, :class_name, :has_member_relation, :type_validator] 5 | end 6 | 7 | def self.build(model, name, options) 8 | model.indirectly_contains name, { has_member_relation: has_member_relation(options), through: proxy_class, foreign_key: proxy_foreign_key, inserted_content_relation: inserted_content_relation }.merge(indirect_options(options)) 9 | model.has_subresource contains_key(options), class_name: list_source_class 10 | model.orders name, through: contains_key(options) 11 | end 12 | 13 | def self.indirect_options(options) 14 | { 15 | class_name: options[:class_name], 16 | type_validator: options[:type_validator] 17 | }.select { |_k, v| v.present? } 18 | end 19 | private_class_method :indirect_options 20 | 21 | def self.has_member_relation(options) 22 | options[:has_member_relation] || ::RDF::Vocab::DC.hasPart 23 | end 24 | private_class_method :has_member_relation 25 | 26 | def self.inserted_content_relation 27 | ::RDF::Vocab::ORE.proxyFor 28 | end 29 | private_class_method :inserted_content_relation 30 | 31 | def self.proxy_class 32 | "ActiveFedora::Aggregation::Proxy" 33 | end 34 | private_class_method :proxy_class 35 | 36 | def self.proxy_foreign_key 37 | :target 38 | end 39 | private_class_method :proxy_foreign_key 40 | 41 | def self.contains_key(options) 42 | options[:through] 43 | end 44 | private_class_method :contains_key 45 | 46 | def self.list_source_class 47 | "ActiveFedora::Aggregation::ListSource" 48 | end 49 | private_class_method :list_source_class 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/active_fedora/associations/singular_association.rb: -------------------------------------------------------------------------------- 1 | module ActiveFedora 2 | module Associations 3 | class SingularAssociation < Association # :nodoc: 4 | # Implements the reader method, e.g. foo.bar for Foo.has_one :bar 5 | # rubocop:disable Style/GuardClause 6 | def reader(force_reload = false) 7 | if force_reload 8 | raise NotImplementedError, "Need to define the uncached method" # TODO 9 | # klass.uncached { reload } 10 | elsif !loaded? || stale_target? 11 | reload 12 | end 13 | target 14 | end 15 | # rubocop:enable Style/GuardClause 16 | 17 | # Implements the writer method, e.g. foo.items= for Foo.has_many :items 18 | def writer(record) 19 | replace(record) 20 | end 21 | 22 | def create(attributes = {}) 23 | new_record(:create, attributes) 24 | end 25 | 26 | def create!(attributes = {}) 27 | build(attributes).tap(&:save!) 28 | end 29 | 30 | def build(attributes = {}) 31 | new_record(:build, attributes) 32 | end 33 | 34 | private 35 | 36 | def find_target 37 | # TODO: this forces a solr query, but I think it's likely we can just lookup from Fedora. 38 | # See https://github.com/samvera/active_fedora/issues/1330 39 | rec = scope.take 40 | rec.tap { |record| set_inverse_instance(record) } 41 | end 42 | 43 | # Implemented by subclasses 44 | def replace(_record) 45 | raise NotImplementedError 46 | end 47 | 48 | def new_record(method, attributes) 49 | attributes = {} # scoped.scope_for_create.merge(attributes || {}) 50 | record = @reflection.send("#{method}_association", attributes) 51 | replace(record) 52 | record 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/active_fedora/attribute_assignment.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/hash/keys' 2 | 3 | module ActiveFedora 4 | module AttributeAssignment 5 | include ActiveModel::ForbiddenAttributesProtection 6 | 7 | # Alias for assign_attributes. 8 | def attributes=(attributes) 9 | assign_attributes(attributes) 10 | end 11 | 12 | # Allows you to set all the attributes by passing in a hash of attributes with 13 | # keys matching the attribute names. 14 | # 15 | # If the passed hash responds to permitted? method and the return value 16 | # of this method is +false+ an ActiveModel::ForbiddenAttributesError 17 | # exception is raised. 18 | # 19 | # class Cat 20 | # include ActiveModel::AttributeAssignment 21 | # attr_accessor :name, :status 22 | # end 23 | # 24 | # cat = Cat.new 25 | # cat.assign_attributes(name: "Gorby", status: "yawning") 26 | # cat.name # => 'Gorby' 27 | # cat.status => 'yawning' 28 | # cat.assign_attributes(status: "sleeping") 29 | # cat.name # => 'Gorby' 30 | # cat.status => 'sleeping' 31 | def assign_attributes(new_attributes) 32 | raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." unless new_attributes.respond_to?(:stringify_keys) 33 | return if new_attributes.blank? 34 | 35 | attributes = new_attributes.stringify_keys 36 | _assign_attributes(sanitize_for_mass_assignment(attributes)) 37 | end 38 | 39 | private 40 | 41 | def _assign_attributes(attributes) 42 | attributes.each do |k, v| 43 | _assign_attribute(k, v) 44 | end 45 | end 46 | 47 | def _assign_attribute(k, v) 48 | raise UnknownAttributeError.new(self, k) unless respond_to?("#{k}=") 49 | public_send("#{k}=", v) 50 | end 51 | end 52 | end 53 | --------------------------------------------------------------------------------