hyrax 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +22 -0
  3. data/.dassie/Gemfile +10 -5
  4. data/.dassie/config/initializers/hyrax.rb +5 -0
  5. data/.dockerignore +3 -0
  6. data/.env +0 -1
  7. data/.rubocop.yml +4 -0
  8. data/CONTAINERS.md +1 -1
  9. data/Dockerfile +12 -6
  10. data/Gemfile +21 -27
  11. data/app/actors/hyrax/actors/base_actor.rb +1 -1
  12. data/app/actors/hyrax/actors/create_with_remote_files_actor.rb +85 -63
  13. data/app/actors/hyrax/actors/create_with_remote_files_ordered_members_actor.rb +7 -42
  14. data/app/controllers/concerns/hyrax/collections_controller_behavior.rb +20 -8
  15. data/app/controllers/concerns/hyrax/embargoes_controller_behavior.rb +21 -9
  16. data/app/controllers/concerns/hyrax/leases_controller_behavior.rb +14 -5
  17. data/app/controllers/concerns/hyrax/works_controller_behavior.rb +22 -3
  18. data/app/controllers/hyrax/admin/workflows_controller.rb +8 -2
  19. data/app/controllers/hyrax/dashboard/collection_members_controller.rb +13 -9
  20. data/app/controllers/hyrax/dashboard/collections_controller.rb +12 -10
  21. data/app/controllers/hyrax/file_sets_controller.rb +49 -13
  22. data/app/controllers/hyrax/permissions_controller.rb +3 -4
  23. data/app/controllers/hyrax/workflow_actions_controller.rb +3 -1
  24. data/app/forms/hyrax/forms/collection_form.rb +7 -3
  25. data/app/forms/hyrax/forms/dashboard/nest_collection_form.rb +24 -2
  26. data/app/forms/hyrax/forms/file_set_form.rb +46 -0
  27. data/app/forms/hyrax/forms/permission.rb +23 -0
  28. data/app/forms/hyrax/forms/permission_template_form.rb +8 -2
  29. data/app/forms/hyrax/forms/resource_form.rb +10 -17
  30. data/app/forms/hyrax/forms/work_form.rb +5 -2
  31. data/app/helpers/hyrax/batch_edits_helper.rb +3 -1
  32. data/app/helpers/hyrax/collections_helper.rb +88 -2
  33. data/app/helpers/hyrax/dashboard_helper_behavior.rb +3 -7
  34. data/app/helpers/hyrax/file_set_helper.rb +25 -6
  35. data/app/helpers/hyrax/work_form_helper.rb +53 -0
  36. data/app/indexers/hyrax/administrative_set_indexer.rb +18 -0
  37. data/app/indexers/hyrax/valkyrie_indexer.rb +3 -3
  38. data/app/inputs/controlled_vocabulary_input.rb +2 -5
  39. data/app/jobs/attach_files_to_work_job.rb +19 -10
  40. data/app/jobs/attach_files_to_work_with_ordered_members_job.rb +6 -5
  41. data/app/jobs/inherit_permissions_job.rb +9 -5
  42. data/app/models/admin_set.rb +6 -25
  43. data/app/models/concerns/hyrax/ability.rb +3 -1
  44. data/app/models/concerns/hyrax/collection_behavior.rb +17 -44
  45. data/app/models/concerns/hyrax/file_set/characterization.rb +18 -12
  46. data/app/models/concerns/hyrax/solr_document_behavior.rb +9 -52
  47. data/app/models/concerns/hyrax/suppressible.rb +5 -0
  48. data/app/models/concerns/hyrax/user.rb +9 -3
  49. data/app/models/hyrax/file_set.rb +6 -0
  50. data/app/models/hyrax/pcdm_collection.rb +1 -0
  51. data/app/models/hyrax/permission_template.rb +98 -12
  52. data/app/models/hyrax/virus_scanner.rb +27 -18
  53. data/app/models/sipity/agent.rb +1 -0
  54. data/app/models/sipity/entity.rb +30 -8
  55. data/app/models/sipity/workflow.rb +1 -0
  56. data/app/models/sipity.rb +42 -0
  57. data/app/presenters/hyrax/admin_set_options_presenter.rb +2 -10
  58. data/app/presenters/hyrax/admin_set_presenter.rb +5 -1
  59. data/app/presenters/hyrax/admin_set_selection_presenter.rb +116 -0
  60. data/app/presenters/hyrax/collection_presenter.rb +31 -6
  61. data/app/presenters/hyrax/file_set_presenter.rb +6 -1
  62. data/app/presenters/hyrax/file_usage.rb +3 -2
  63. data/app/presenters/hyrax/stats_usage_presenter.rb +2 -1
  64. data/app/presenters/hyrax/trophy_presenter.rb +33 -4
  65. data/app/presenters/hyrax/user_profile_presenter.rb +11 -1
  66. data/app/presenters/hyrax/version_list_presenter.rb +19 -0
  67. data/app/presenters/hyrax/version_presenter.rb +3 -2
  68. data/app/presenters/hyrax/work_show_presenter.rb +25 -4
  69. data/app/presenters/hyrax/work_usage.rb +5 -3
  70. data/app/renderers/hyrax/renderers/attribute_renderer.rb +10 -2
  71. data/app/search_builders/hyrax/admin_set_search_builder.rb +1 -1
  72. data/app/search_builders/hyrax/my/collections_search_builder.rb +1 -1
  73. data/app/services/hyrax/admin_set_create_service.rb +3 -1
  74. data/app/services/hyrax/collections/collection_member_search_service.rb +72 -0
  75. data/app/services/hyrax/collections/collection_member_service.rb +112 -27
  76. data/app/services/hyrax/collections/migration_service.rb +4 -2
  77. data/app/services/hyrax/collections/nested_collection_persistence_service.rb +12 -13
  78. data/app/services/hyrax/collections/nested_collection_query_service.rb +2 -0
  79. data/app/services/hyrax/collections/permissions_create_service.rb +6 -4
  80. data/app/services/hyrax/contextual_path.rb +23 -0
  81. data/app/services/hyrax/custom_queries/find_file_metadata.rb +7 -5
  82. data/app/services/hyrax/custom_queries/navigators/parent_collections_navigator.rb +46 -0
  83. data/app/services/hyrax/edit_permissions_service.rb +27 -20
  84. data/app/services/hyrax/find_objects_via_solr_service.rb +11 -7
  85. data/app/services/hyrax/multiple_membership_checker.rb +51 -31
  86. data/app/services/hyrax/resource_status.rb +7 -0
  87. data/app/services/hyrax/search_service.rb +4 -2
  88. data/app/services/hyrax/solr_query_builder_service.rb +29 -6
  89. data/app/services/hyrax/solr_query_service.rb +224 -0
  90. data/app/services/hyrax/solr_service.rb +8 -1
  91. data/app/services/hyrax/statistics/depositors/summary.rb +2 -1
  92. data/app/services/hyrax/work_uploads_handler.rb +17 -2
  93. data/app/services/hyrax/workflow/actionable_objects.rb +70 -0
  94. data/app/services/hyrax/workflow/object_in_workflow_decorator.rb +31 -0
  95. data/app/services/hyrax/workflow/status_list_service.rb +43 -13
  96. data/app/views/hyrax/base/_form_relationships.html.erb +1 -2
  97. data/app/views/hyrax/base/_form_rendering.html.erb +1 -1
  98. data/app/views/hyrax/base/_form_representative.html.erb +1 -1
  99. data/app/views/hyrax/base/_form_thumbnail.html.erb +1 -1
  100. data/app/views/hyrax/base/_guts4form.html.erb +2 -2
  101. data/app/views/hyrax/base/_representative_media.html.erb +1 -1
  102. data/app/views/hyrax/base/_show_actions.html.erb +1 -1
  103. data/app/views/hyrax/dashboard/collections/_form.html.erb +3 -3
  104. data/app/views/hyrax/dashboard/collections/_list_collections.html.erb +1 -1
  105. data/app/views/hyrax/dashboard/collections/edit.html.erb +4 -2
  106. data/app/views/hyrax/dashboard/collections/new.html.erb +4 -2
  107. data/app/views/hyrax/dashboard/collections/show.html.erb +1 -1
  108. data/app/views/hyrax/file_sets/edit.html.erb +1 -1
  109. data/app/views/hyrax/file_sets/media_display/_audio.html.erb +1 -1
  110. data/app/views/hyrax/file_sets/media_display/_default.html.erb +1 -1
  111. data/app/views/hyrax/file_sets/media_display/_image.html.erb +1 -1
  112. data/app/views/hyrax/file_sets/media_display/_office_document.html.erb +1 -1
  113. data/app/views/hyrax/file_sets/media_display/_pdf.html.erb +1 -1
  114. data/app/views/hyrax/file_sets/media_display/_video.html.erb +1 -1
  115. data/app/views/hyrax/file_sets/show.html.erb +1 -1
  116. data/app/views/hyrax/my/_admin_set_action_menu.html.erb +0 -11
  117. data/app/views/hyrax/my/_collection_action_menu.html.erb +1 -2
  118. data/app/views/hyrax/my/collections/_list_collections.html.erb +1 -1
  119. data/app/views/hyrax/my/collections/_modal_add_subcollection.html.erb +3 -5
  120. data/bin/solrcloud-assign-configset.sh +8 -5
  121. data/bin/solrcloud-upload-configset.sh +4 -2
  122. data/chart/hyrax/Chart.yaml +3 -3
  123. data/chart/hyrax/README.md +47 -1
  124. data/chart/hyrax/templates/_helpers.tpl +1 -1
  125. data/chart/hyrax/templates/configmap-env.yaml +1 -3
  126. data/chart/hyrax/templates/deployment-worker.yaml +6 -3
  127. data/chart/hyrax/templates/deployment.yaml +8 -3
  128. data/chart/hyrax/values.yaml +12 -0
  129. data/config/brakeman.ignore +2 -2
  130. data/config/locales/hyrax.de.yml +1 -1
  131. data/config/locales/hyrax.en.yml +1 -1
  132. data/config/locales/hyrax.es.yml +1 -1
  133. data/config/locales/hyrax.fr.yml +1 -1
  134. data/config/locales/hyrax.it.yml +1 -1
  135. data/config/locales/hyrax.pt-BR.yml +1 -1
  136. data/config/locales/hyrax.zh.yml +1 -1
  137. data/docker-compose.yml +1 -0
  138. data/documentation/developing-your-hyrax-based-app.md +1 -1
  139. data/documentation/legacyREADME.md +1 -1
  140. data/lib/generators/hyrax/templates/config/initializers/hyrax.rb +5 -0
  141. data/lib/hyrax/active_fedora_dummy_model.rb +62 -0
  142. data/lib/hyrax/configuration.rb +8 -0
  143. data/lib/hyrax/engine.rb +1 -0
  144. data/lib/hyrax/errors.rb +2 -0
  145. data/lib/hyrax/specs/capybara.rb +3 -1
  146. data/lib/hyrax/specs/shared_specs/valkyrie_storage_versions.rb +9 -0
  147. data/lib/hyrax/transactions/container.rb +21 -0
  148. data/lib/hyrax/transactions/file_set_destroy.rb +21 -0
  149. data/lib/hyrax/transactions/steps/add_file_sets.rb +3 -2
  150. data/lib/hyrax/transactions/steps/add_to_parent.rb +36 -0
  151. data/lib/hyrax/transactions/steps/remove_file_set_from_work.rb +47 -0
  152. data/lib/hyrax/transactions/work_create.rb +2 -1
  153. data/lib/hyrax/valkyrie_can_can_adapter.rb +1 -0
  154. data/lib/hyrax/version.rb +1 -1
  155. data/lib/hyrax.rb +9 -0
  156. data/lib/tasks/collection_type_global_id.rake +1 -1
  157. data/lib/tasks/regenerate_derivatives.rake +12 -0
  158. data/lib/wings/orm_converter.rb +18 -2
  159. data/lib/wings/setup.rb +1 -0
  160. data/lib/wings/valkyrie/storage.rb +56 -1
  161. data/template.rb +1 -1
  162. metadata +17 -2
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
+ # @deprecated This class is being replaced by Hyrax::SolrQueryService #get_objects method.
4
+ #
3
5
  # Methods in this class search solr to get the ids and then use the query service to find the objects.
4
6
  class FindObjectsViaSolrService
5
7
  class_attribute :solr_query_builder, :solr_service, :query_service
6
- self.solr_query_builder = Hyrax::SolrQueryBuilderService
8
+ self.solr_query_builder = Hyrax::SolrQueryService
7
9
  self.solr_service = Hyrax::SolrService
8
10
  self.query_service = Hyrax.query_service
9
11
 
@@ -14,13 +16,15 @@ module Hyrax
14
16
  # @param join_with [String] the value we're joining the clauses with (default: ' OR ' for backward compatibility with ActiveFedora where)
15
17
  # @param type [String] The type of query to run. Either 'raw' or 'field' (default: 'field')
16
18
  # @param use_valkyrie [Boolean] if true, return Valkyrie resource(s); otherwise, return ActiveFedora object(s)
17
- # @return [Array<ActiveFedora|Valkyrie::Resource>] objects matching the query
19
+ # @return [Array<ActiveFedora::Base|Valkyrie::Resource>] objects matching the query
18
20
  def find_for_model_by_field_pairs(model:, field_pairs:, join_with: ' OR ', type: 'field', use_valkyrie: Hyrax.config.use_valkyrie?)
19
- return model.where(field_pairs).to_a unless use_valkyrie
20
- query = solr_query_builder.construct_query_for_model(model, field_pairs, join_with, type)
21
- results = solr_service.query(query)
22
- ids = results.map(&:id)
23
- query_service.find_many_by_ids(ids: ids).to_a
21
+ Deprecation.warn("'##{__method__}' will be removed in Hyrax 4.0. " \
22
+ "Instead, use 'Hyrax::SolrQueryService.new.with_model(...).with_field_pairs(...).get_objects'.")
23
+
24
+ solr_query_builder.new
25
+ .with_model(model: model)
26
+ .with_field_pairs(field_pairs: field_pairs, join_with: join_with, type: type)
27
+ .get_objects(use_valkyrie: use_valkyrie).to_a
24
28
  end
25
29
  end
26
30
  end
@@ -6,8 +6,7 @@ module Hyrax
6
6
  class MultipleMembershipChecker
7
7
  attr_reader :item
8
8
 
9
- # @param [#member_of_collection_ids] item an object that belongs to
10
- # collections
9
+ # @param [#member_of_collection_ids] item an object that belongs to collections
11
10
  def initialize(item:)
12
11
  @item = item
13
12
  end
@@ -20,57 +19,78 @@ module Hyrax
20
19
  # `allow_multiple_membership` as `false` require that its members do not
21
20
  # also belong to other collections of the same type.
22
21
  #
23
- # There are two contexts in which memberships are checked: when doing a
24
- # wholesale replacement and when making an incremental change, such as
25
- # adding a single collection membership to an object. In the former case,
26
- # `#check` only scans the passed-in collection identifiers. In the latter,
27
- # `#check` must also scan the collections to which an object currently
28
- # belongs for potential conflicts.
29
- #
30
22
  # @param collection_ids [Array<String>] a list of collection identifiers
31
- # @param include_current_members [Boolean] a flag to also scan an object's
32
- # current collection memberships
23
+ # @param include_current_members [Boolean] if true, include item's existing
24
+ # collections in check; else if false, check passed in collections only
25
+ # * use `false` when collection_ids includes proposed new collections and existing
26
+ # collections (@see Hyrax::Actors::CollectionsMembershipActor #valid_membership?)
27
+ # * use `true` when collection_ids includes proposed new collections only
28
+ # (@see Hyrax::Collections::CollectionMemberService #add_member)
33
29
  #
34
30
  # @return [nil, String] nil if no conflicts; an error message string if so
35
31
  def check(collection_ids:, include_current_members: false)
36
- # short-circuit if no single membership types have been created
37
- return if collection_type_gids_that_disallow_multiple_membership.blank?
38
- # short-circuit if no new single_membership_collections passed in
39
- new_single_membership_collections = single_membership_collections(collection_ids)
40
- return if new_single_membership_collections.blank?
41
- collections_to_check = new_single_membership_collections
42
- # No need to check current members when coming in from the ActorStack, which does a wholesale collection membership replacement
43
- collections_to_check |= single_membership_collections(item.member_of_collection_ids) if include_current_members
44
- problematic_collections = collections_to_check.uniq(&:id)
45
- .group_by(&:collection_type_gid)
46
- .select { |_gid, list| list.count > 1 }
47
- return if problematic_collections.blank?
32
+ return unless single_membership_collection_types_exist?
33
+
34
+ proposed_single_membership_collections = filter_to_single_membership_collections(collection_ids)
35
+ return if proposed_single_membership_collections.blank?
36
+
37
+ collections_to_check = collections_to_check(proposed_single_membership_collections,
38
+ include_current_members)
39
+ problematic_collections = check_collections(collections_to_check)
48
40
  build_error_message(problematic_collections)
49
41
  end
50
42
 
51
43
  private
52
44
 
53
- def single_membership_collections(collection_ids)
54
- return [] if collection_ids.blank?
45
+ def single_membership_collection_types_exist?
46
+ single_membership_collection_types_gids.present?
47
+ end
55
48
 
49
+ def single_membership_collection_types_gids
50
+ @single_membership_collection_types_gids ||=
51
+ Hyrax::CollectionType.gids_that_do_not_allow_multiple_membership&.map(&:to_s)
52
+ end
53
+
54
+ def filter_to_single_membership_collections(collection_ids)
55
+ return [] if collection_ids.blank?
56
56
  field_pairs = {
57
- :id => collection_ids,
58
- Hyrax.config.collection_type_index_field.to_sym => collection_type_gids_that_disallow_multiple_membership
57
+ Hyrax.config.collection_type_index_field.to_sym => single_membership_collection_types_gids
59
58
  }
60
- Hyrax::FindObjectsViaSolrService.find_for_model_by_field_pairs(model: ::Collection, field_pairs: field_pairs, use_valkyrie: true)
59
+ Hyrax::SolrQueryService.new
60
+ .with_generic_type(generic_type: "Collection")
61
+ .with_ids(ids: Array[collection_ids])
62
+ .with_field_pairs(field_pairs: field_pairs, join_with: ' OR ')
63
+ .get_objects(use_valkyrie: true).to_a
61
64
  end
62
65
 
63
- def collection_type_gids_that_disallow_multiple_membership
64
- Hyrax::CollectionType.gids_that_do_not_allow_multiple_membership
66
+ def collections_to_check(proposed, include_current_members)
67
+ # ActorStack does a wholesale collection membership replacement, such that
68
+ # proposed collections include existing and new collections. Parameter
69
+ # `include_current_members` will be false when coming from the actor stack
70
+ # to prevent member items being passed in and then added here as well.
71
+ return proposed unless include_current_members
72
+ proposed | filter_to_single_membership_collections(item.member_of_collection_ids)
73
+ end
74
+
75
+ def check_collections(collections_to_check)
76
+ # uniq insures we include a collection only once when it is in the list multiple
77
+ # group_by groups collections of the same collection type together
78
+ # select keeps only collection type groups that have more than one collection
79
+ # of the single collection type
80
+ collections_to_check.uniq(&:id)
81
+ .group_by(&:collection_type_gid)
82
+ .select { |_gid, list| list.count > 1 }
65
83
  end
66
84
 
67
85
  def build_error_message(problematic_collections)
86
+ return if problematic_collections.blank?
68
87
  error_message_clauses = problematic_collections.map do |gid, list|
69
88
  I18n.t('hyrax.admin.collection_types.multiple_membership_checker.error_type_and_collections',
70
89
  type: collection_type_title_from_gid(gid),
71
90
  collections: collection_titles_from_list(list))
72
91
  end
73
- "#{I18n.t('hyrax.admin.collection_types.multiple_membership_checker.error_preamble')}#{error_message_clauses.join('; ')}"
92
+ "#{I18n.t('hyrax.admin.collection_types.multiple_membership_checker.error_preamble')}" \
93
+ "#{error_message_clauses.join('; ')}"
74
94
  end
75
95
 
76
96
  def collection_type_title_from_gid(gid)
@@ -36,6 +36,13 @@ module Hyrax
36
36
  self.resource = resource
37
37
  end
38
38
 
39
+ ##
40
+ # @param [#state] resource
41
+ # @return [Boolean]
42
+ def self.inactive?(resource:)
43
+ new(resource: resource).inactive?
44
+ end
45
+
39
46
  ##
40
47
  # @return [Boolean]
41
48
  # @raise [NoMethodError] if the resource doesn't have a state attribute
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # SearchService returns search results from the repository
4
3
  module Hyrax
5
- # Copied from Blacklight 7
4
+ ##
5
+ # Returns search results from the repository.
6
+ #
7
+ # @note Adapted from Blacklight 7
6
8
  class SearchService
7
9
  def initialize(config:, user_params: nil, search_builder_class: config.search_builder_class, **context)
8
10
  @blacklight_config = config
@@ -1,16 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
+ ##
4
+ # @deprecated
5
+ # This class is being replaced by Hyrax::SolrQueryService.
6
+ #
3
7
  # Methods in this class are from/based on ActiveFedora::SolrQueryBuilder
8
+ #
9
+ # @see Hyrax::SolrQueryService
4
10
  class SolrQueryBuilderService
5
11
  class << self
6
12
  # Construct a solr query for a list of ids
7
13
  # This is used to get a solr response based on the list of ids in an object's RELS-EXT relationhsips
8
14
  # If the id_array is empty, defaults to a query of "id:NEVER_USE_THIS_ID", which will return an empty solr response
9
15
  # @param [Array] id_array the ids that you want included in the query
16
+ # @return [String] a solr query
17
+ # @example
18
+ # construct_query_for_ids(['a1', 'b2'])
19
+ # # => "{!terms f=id}a1,b2"
20
+ # @deprecated
10
21
  def construct_query_for_ids(id_array)
22
+ Deprecation.warn("'##{__method__}' will be removed in Hyrax 4.0. " \
23
+ "Instead, use 'Hyrax::SolrQueryService.new.with_ids'.")
11
24
  ids = id_array.reject(&:blank?)
12
25
  return "id:NEVER_USE_THIS_ID" if ids.empty?
13
- "{!terms f=#{Hyrax.config.id_field}}#{ids.join(',')}"
26
+ Hyrax::SolrQueryService.new.with_ids(ids: id_array).build
14
27
  end
15
28
 
16
29
  # Construct a solr query from a list of pairs (e.g. [field name, values])
@@ -21,14 +34,19 @@ module Hyrax
21
34
  # @example
22
35
  # construct_query([['library_id_ssim', '123'], ['owner_ssim', 'Fred']])
23
36
  # # => "_query_:\"{!field f=library_id_ssim}123\" AND _query_:\"{!field f=owner_ssim}Fred\""
37
+ # @deprecated
24
38
  def construct_query(field_pairs, join_with = default_join_with, type = 'field')
25
- clauses = pairs_to_clauses(field_pairs, type)
26
- return "" if clauses.count.zero?
27
- return clauses.first if clauses.count == 1
28
- "(#{clauses.join(join_with)})"
39
+ Deprecation.warn("'##{__method__}' will be removed in Hyrax 4.0. " \
40
+ "Instead, use 'Hyrax::SolrQueryService.new.with_field_pairs'.")
41
+ Hyrax::SolrQueryService.new.with_field_pairs(field_pairs: field_pairs,
42
+ join_with: join_with,
43
+ type: type).build
29
44
  end
30
45
 
46
+ # @deprecated
31
47
  def default_join_with
48
+ Deprecation.warn("'##{__method__}' will be removed in Hyrax 4.0. " \
49
+ "There will not be a replacement for this method. See Hyrax::SolrQueryService which is replacing this class.")
32
50
  ' AND '
33
51
  end
34
52
 
@@ -41,9 +59,14 @@ module Hyrax
41
59
  # @example
42
60
  # construct_query(Collection, [['library_id_ssim', '123'], ['owner_ssim', 'Fred']])
43
61
  # # => "_query_:\"{!field f=has_model_ssim}Collection\" AND _query_:\"{!field f=library_id_ssim}123\" AND _query_:\"{!field f=owner_ssim}Fred\""
62
+ # @deprecated
44
63
  def construct_query_for_model(model, field_pairs, join_with = default_join_with, type = 'field')
64
+ Deprecation.warn("'##{__method__}' will be removed in Hyrax 4.0. " \
65
+ "Instead, use 'Hyrax::SolrQueryService.new.with_model'.")
45
66
  field_pairs["has_model_ssim"] = model.to_s
46
- construct_query(field_pairs, join_with, type)
67
+ Hyrax::SolrQueryService.new.with_field_pairs(field_pairs: field_pairs,
68
+ join_with: join_with,
69
+ type: type).build
47
70
  end
48
71
 
49
72
  private
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ ##
4
+ # Supports building and executing a solr query.
5
+ #
6
+ # @note Methods in this class are providing functionality previously supported by
7
+ # ActiveFedora::SolrQueryBuilder.
8
+ class SolrQueryService < ::SearchBuilder # rubocop:disable Metrics/ClassLength
9
+ class_attribute :query_service
10
+ self.query_service = Hyrax.query_service
11
+
12
+ attr_reader :query, :solr_service
13
+
14
+ def initialize(query: [], solr_service: Hyrax::SolrService)
15
+ @query = query
16
+ @solr_service = solr_service
17
+ end
18
+
19
+ ##
20
+ # @api private
21
+ # @see Blacklight::Configuration#document_model
22
+ #
23
+ # @return [Class] the model class to use for solr documents
24
+ def self.document_model
25
+ CatalogController.blacklight_config.document_model
26
+ end
27
+
28
+ ##
29
+ # @return [Hash] the results returned from solr for the current query
30
+ def get
31
+ solr_service.get(build)
32
+ end
33
+
34
+ ##
35
+ # @return [Enumerable<SolrDocument>]
36
+ def solr_documents
37
+ get['response']['docs'].map { |doc| self.class.document_model.new(doc) }
38
+ end
39
+
40
+ ##
41
+ # @return [Array<String>] ids of documents matching the current query
42
+ def get_ids # rubocop:disable Naming/AccessorMethodName
43
+ results = get
44
+ results['response']['docs'].map { |doc| doc['id'] }
45
+ end
46
+
47
+ ##
48
+ # @return [Array<Valkyrie::Resource|ActiveFedora::Base>] objects matching the current query
49
+ def get_objects(use_valkyrie: Hyrax.config.use_valkyrie?)
50
+ ids = get_ids
51
+ return ids.map { |id| ActiveFedora::Base.find(id) } unless use_valkyrie
52
+ query_service.find_many_by_ids(ids: ids)
53
+ end
54
+
55
+ ##
56
+ # @return [Integer] the number of results that match the query in solr
57
+ def count
58
+ solr_service.count(build)
59
+ end
60
+
61
+ ##
62
+ # @return [String] the combined query that can be submitted to solr
63
+ def build
64
+ return 'id:NEVER_USE_THIS_ID' if @query.blank? # forces this method to always return a valid solr query
65
+ @query.join(' AND ')
66
+ end
67
+
68
+ ##
69
+ # @return [Hyrax::SolrQueryService] the existing service with the query reset to empty
70
+ def reset
71
+ @query = []
72
+ self
73
+ end
74
+
75
+ ##
76
+ # @param ids [Array] id_array the ids that you want included in the query
77
+ # @return [Hyrax::SolrQueryService] the existing service with id query appended
78
+ def with_ids(ids: [])
79
+ ids = ids.reject(&:blank?)
80
+ raise ArgumentError, "Expected there to be at least one non-blank id." if ids.blank?
81
+ id_query = construct_query_for_ids(ids)
82
+ @query += [id_query]
83
+ self
84
+ end
85
+
86
+ ##
87
+ # @param model [#to_s] a class from the model (e.g. Hyrax::Work, Hyrax::FileSet, etc.)
88
+ # @return [SolrQueryService] the existing service with model query appended
89
+ def with_model(model:)
90
+ model_query = construct_query_for_model(model)
91
+ @query += [model_query]
92
+ self
93
+ end
94
+
95
+ ##
96
+ # @param generic_type [String] (Default: Work)
97
+ # @return [SolrQueryService] the existing service with model query appended
98
+ def with_generic_type(generic_type: 'Work')
99
+ # TODO: Generic type was originally stored as `sim`. Since it is never multi-valued, it is moving to being stored
100
+ # as `si`. Until a migration is created to correct existing solr docs, this query searches in both fields.
101
+ field_pairs = { generic_type_si: generic_type, generic_type_sim: generic_type }
102
+ type_query = construct_query_for_pairs(field_pairs, ' OR ', 'field')
103
+ @query += [type_query]
104
+ self
105
+ end
106
+
107
+ ##
108
+ # @param field_pairs [Hash] a list of pairs of property name and values (e.g. { field1: values, field2: values })
109
+ # @param join_with [String] the connector used to join the field pairs (default: ' AND ')
110
+ # @param type [String] type of query to run. Either 'raw' or 'field' (default: 'field')
111
+ # @return [SolrQueryService] the existing service with field_pair query appended
112
+ def with_field_pairs(field_pairs: {}, join_with: default_join_with, type: 'field')
113
+ pairs_query = construct_query_for_pairs(field_pairs, join_with, type)
114
+ return self if pairs_query.blank?
115
+ @query += [pairs_query]
116
+ self
117
+ end
118
+
119
+ ##
120
+ # @param ability [???] the user's abilities
121
+ # @param action [Symbol] the action the user is taking (e.g. :index, :edit, :show, etc.) (default: :index)
122
+ # @return [SolrQueryService] the existing service with access filters query appended
123
+ def accessible_by(ability:, action: :index)
124
+ access_filters_query = construct_query_for_ability(ability, action)
125
+ @query += [access_filters_query] if access_filters_query.present?
126
+ self
127
+ end
128
+
129
+ private
130
+
131
+ # Construct a solr query for a list of ids
132
+ # @param [Array] ids to be included in the query
133
+ # @return [String] a solr query
134
+ # @example
135
+ # construct_query_for_ids(['id1', 'id2'])
136
+ # # => "{!terms f=id}id1,id2"
137
+ def construct_query_for_ids(ids)
138
+ "{!terms f=#{Hyrax.config.id_field}}#{ids.join(',')}"
139
+ end
140
+
141
+ # Construct a solr query from a list of pairs (e.g. { field1: values, field2: values })
142
+ # @param [Hash] field_pairs a list of pairs of property name and values
143
+ # @param [String] join_with the value we're joining the clauses with (default: ' AND ')
144
+ # @param [String] type of query to run. Either 'raw' or 'field' (default: 'field')
145
+ # @return [String] a solr query
146
+ # @example
147
+ # construct_query([['library_id_ssim', '123'], ['owner_ssim', 'Fred']])
148
+ # # => "_query_:\"{!field f=library_id_ssim}123\" AND _query_:\"{!field f=owner_ssim}Fred\""
149
+ def construct_query_for_pairs(field_pairs, join_with = default_join_with, type = 'field')
150
+ clauses = pairs_to_clauses(field_pairs, type)
151
+ return "" if clauses.count.zero?
152
+ return clauses.first if clauses.count == 1
153
+ "(#{clauses.join(join_with)})"
154
+ end
155
+
156
+ # Construct a solr query from the model (e.g. Collection, Monograph)
157
+ # @param [Class] model class
158
+ # @return [String] a solr query
159
+ # @example
160
+ # construct_query_for_model(Monograph)
161
+ # # => "_query_:\"{!field f=has_model_ssim}Monograph\""
162
+ def construct_query_for_model(model)
163
+ field_pairs = { "has_model_ssim" => model.to_s }
164
+ construct_query_for_pairs(field_pairs)
165
+ end
166
+
167
+ # Construct a solr query based on a User's abilities and the action they taking
168
+ # @param ability [???] the user's abilities
169
+ # @param action [Symbol] the action the user is taking (e.g. :index, :edit, :show, etc.) (default: :index)
170
+ # @return [String] a solr query
171
+ # @example
172
+ # construct_query_for_ability(user, :edit)
173
+ # # => "(({!terms f=edit_access_group_ssim}public,user_group_A}) OR " \
174
+ # "edit_access_person_ssim:#{user@example.com})"
175
+ def construct_query_for_ability(ability, action)
176
+ permission_types = case action
177
+ when :index then [:discover, :read, :edit]
178
+ when :show, :read then [:read, :edit]
179
+ when :update, :edit, :create, :new, :destroy then [:edit]
180
+ end
181
+ filters = gated_discovery_filters(permission_types, ability).join(' OR ')
182
+ return "" if filters.blank?
183
+ "(#{filters})"
184
+ end
185
+
186
+ def default_join_with
187
+ ' AND '
188
+ end
189
+
190
+ # @param [Array<Array>] pairs a list of (key, value) pairs. The value itself may
191
+ # @param [String] type The type of query to run. Either 'raw' or 'field'
192
+ # @return [Hash] a list of solr clauses
193
+ def pairs_to_clauses(pairs, type)
194
+ pairs.flat_map do |field, value|
195
+ condition_to_clauses(field, value, type)
196
+ end
197
+ end
198
+
199
+ # @param [String] field
200
+ # @param [String, Array<String>] values
201
+ # @param [String] type The type of query to run. Either 'raw' or 'field'
202
+ # @return [Array<String>]
203
+ def condition_to_clauses(field, values, type)
204
+ values = Array(values)
205
+ values << nil if values.empty?
206
+ values.map do |value|
207
+ if value.present?
208
+ query_clause(type, field, value)
209
+ else
210
+ # Check that the field is not present. In SQL: "WHERE field IS NULL"
211
+ "-#{field}:[* TO *]"
212
+ end
213
+ end
214
+ end
215
+
216
+ # Create a raw query clause suitable for sending to solr as an fq element
217
+ # @param [String] type The type of query to run. Either 'raw' or 'field'
218
+ # @param [String] key
219
+ # @param [String] value
220
+ def query_clause(type, key, value)
221
+ "_query_:\"{!#{type} f=#{key}}#{value.gsub('"', '\"')}\""
222
+ end
223
+ end
224
+ end