# frozen_string_literal: true module Bulkrax # rubocop:disable Metrics/ClassLength class ObjectFactory < ObjectFactoryInterface include Bulkrax::FileFactory ## # @!group Class Method Interface ## # @note This does not save either object. We need to do that in another # loop. Why? Because we might be adding many items to the parent. def self.add_child_to_parent_work(parent:, child:) return true if parent.ordered_members.to_a.include?(child) parent.ordered_members << child end def self.add_resource_to_collection(collection:, resource:, user:) collection.try(:reindex_extent=, Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX) if defined?(Hyrax::Adapters::NestingIndexAdapter) resource.member_of_collections << collection save!(resource: resource, user: user) end def self.update_index_for_file_sets_of(resource:) resource.file_sets.each(&:update_index) if resource.respond_to?(:file_sets) end ## # @see Bulkrax::ObjectFactoryInterface def self.export_properties # TODO: Consider how this may or may not work for Valkyrie properties = Bulkrax.curation_concerns.map { |work| work.properties.keys }.flatten.uniq.sort properties.reject { |prop| Bulkrax.reserved_properties.include?(prop) } end def self.field_multi_value?(field:, model:) return false unless field_supported?(field: field, model: model) return false unless model.singleton_methods.include?(:properties) model&.properties&.[](field)&.[]("multiple") end def self.field_supported?(field:, model:) model.method_defined?(field) && model.properties[field].present? end def self.file_sets_for(resource:) return [] if resource.blank? return [resource] if resource.is_a?(Bulkrax.file_model_class) resource.file_sets end ## # # @see Bulkrax::ObjectFactoryInterface def self.find(id) ActiveFedora::Base.find(id) rescue ActiveFedora::ObjectNotFoundError => e raise ObjectFactoryInterface::ObjectNotFoundError, e.message end def self.find_or_create_default_admin_set # NOTE: Hyrax 5+ removed this method AdminSet.find_or_create_default_admin_set_id end def self.publish(**) return true end ## # @param value [String] # @param klass [Class, #where] # @param field [String, Symbol] A convenience parameter where we pass the # same value to search_field and name_field. # @param search_field [String, Symbol] the Solr field name # (e.g. "title_tesim") # @param name_field [String] the ActiveFedora::Base property name # (e.g. "title") # @param verify_property [TrueClass] when true, verify that the given :klass # # @return [NilClass] when no object is found. # @return [ActiveFedora::Base] when a match is found, an instance of given # :klass # rubocop:disable Metrics/ParameterLists # # @note HEY WE'RE USING THIS FOR A WINGS CUSTOM QUERY. BE CAREFUL WITH # REMOVING IT. # # @see # {Wings::CustomQueries::FindBySourceIdentifier#find_by_model_and_property_value} def self.search_by_property(value:, klass:, field: nil, search_field: nil, name_field: nil, verify_property: false) return nil unless klass.respond_to?(:where) # We're not going to try to match nil nor "". return if value.blank? return if verify_property && !klass.properties.keys.include?(search_field) search_field ||= field name_field ||= field raise "You must provide either (search_field AND name_field) OR field parameters" if search_field.nil? || name_field.nil? # NOTE: Query can return partial matches (something6 matches both # something6 and something68) so we need to weed out any that are not the # correct full match. But other items might be in the multivalued field, # so we have to go through them one at a time. # # A ssi field is string, so we're looking at exact matches. # A tesi field is text, so partial matches work. # # We need to wrap the result in an Array, else we might have a scalar that # will result again in partial matches. match = klass.where(search_field => value).detect do |m| # Don't use Array.wrap as we likely have an ActiveTriples::Relation # which defiantly claims to be an Array yet does not behave consistently # with an Array. Hopefully the name_field is not a Date or Time object, # Because that too will be a mess. Array(m.send(name_field)).include?(value) end return match if match end # rubocop:enable Metrics/ParameterLists def self.query(q, **kwargs) ActiveFedora::SolrService.query(q, **kwargs) end def self.clean! super do ActiveFedora::Cleaner.clean! end end def self.solr_name(field_name) if defined?(Hyrax) Hyrax.index_field_mapper.solr_name(field_name) else ActiveFedora.index_field_mapper.solr_name(field_name) end end def self.ordered_file_sets_for(object) object&.ordered_members.to_a.select(&:file_set?) end def self.save!(resource:, **) resource.save! end def self.update_index(resources: []) Array(resources).each(&:update_index) end # @!endgroup Class Method Interface ## def find_by_id return false if attributes[:id].blank? # Rails / Ruby upgrade, we moved from :exists? to :exist? However we want to continue (for a # bit) to support older versions. method_name = klass.respond_to?(:exist?) ? :exist? : :exists? klass.find(attributes[:id]) if klass.send(method_name, attributes[:id]) rescue Valkyrie::Persistence::ObjectNotFoundError false end def delete(_user) obj = find return false unless obj obj.delete(eradicate: true) end private # @param [Hash] attrs the attributes to put in the environment # @return [Hyrax::Actors::Environment] def environment(attrs) Hyrax::Actors::Environment.new(object, Ability.new(@user), attrs) end def work_actor Hyrax::CurationConcern.actor end def create_work(attrs) work_actor.create(environment(attrs)) end def update_work(attrs) work_actor.update(environment(attrs)) end def create_collection(attrs) attrs = clean_attrs(attrs) attrs = collection_type(attrs) object.attributes = attrs object.save! end def update_collection(attrs) object.attributes = attrs object.save! end # This method is heavily inspired by Hyrax's AttachFilesToWorkJob def create_file_set(attrs) _, work = find_record(attributes[related_parents_parsed_mapping].first, importer_run_id) work_permissions = work.permissions.map(&:to_hash) attrs = clean_attrs(attrs) file_set_attrs = attrs.slice(*object.attributes.keys) object.assign_attributes(file_set_attrs) attrs['uploaded_files']&.each do |uploaded_file_id| uploaded_file = ::Hyrax::UploadedFile.find(uploaded_file_id) next if uploaded_file.file_set_uri.present? create_file_set_actor(attrs, work, work_permissions, uploaded_file) end attrs['remote_files']&.each do |remote_file| create_file_set_actor(attrs, work, work_permissions, nil, remote_file) end object.save! end def create_file_set_actor(attrs, work, work_permissions, uploaded_file, remote_file = nil) actor = ::Hyrax::Actors::FileSetActor.new(object, @user) uploaded_file&.update(file_set_uri: actor.file_set.uri) actor.file_set.permissions_attributes = work_permissions actor.create_metadata(attrs) actor.create_content(uploaded_file) if uploaded_file actor.attach_to_work(work, attrs) handle_remote_file(remote_file: remote_file, actor: actor) if remote_file end def update_file_set(attrs) file_set_attrs = attrs.slice(*object.attributes.keys) actor = ::Hyrax::Actors::FileSetActor.new(object, @user) attrs['remote_files']&.each do |remote_file| handle_remote_file(remote_file: remote_file, actor: actor) end actor.update_metadata(file_set_attrs) end def handle_remote_file(remote_file:, actor:) actor.file_set.label = remote_file['file_name'] actor.file_set.import_url = remote_file['url'] auth_header = remote_file.fetch('auth_header', {}) ImportUrlJob.perform_now(actor.file_set, file_set_operation_for(user: @user), auth_header) end def file_set_operation_for(user:) Hyrax::Operation.create!(user: user, operation_type: "Attach Remote File") end end # rubocop:enable Metrics/ClassLength end