valkyrie 2.0.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +65 -56
  3. data/.lando.yml +58 -0
  4. data/.rubocop.yml +4 -1
  5. data/.tool-versions +1 -1
  6. data/Appraisals +4 -4
  7. data/CHANGELOG.md +134 -0
  8. data/README.md +21 -49
  9. data/Rakefile +26 -20
  10. data/db/config.yml +3 -10
  11. data/db/schema.rb +0 -40
  12. data/gemfiles/activerecord_5_2.gemfile +2 -0
  13. data/gemfiles/{activerecord_5_1.gemfile → activerecord_6_0.gemfile} +3 -1
  14. data/lib/generators/valkyrie/resource_generator.rb +3 -3
  15. data/lib/valkyrie.rb +33 -15
  16. data/lib/valkyrie/change_set.rb +3 -3
  17. data/lib/valkyrie/id.rb +26 -3
  18. data/lib/valkyrie/indexers/access_controls_indexer.rb +17 -17
  19. data/lib/valkyrie/logging.rb +72 -0
  20. data/lib/valkyrie/persistence/composite_persister.rb +1 -1
  21. data/lib/valkyrie/persistence/fedora.rb +2 -0
  22. data/lib/valkyrie/persistence/fedora/list_node.rb +46 -49
  23. data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +7 -3
  24. data/lib/valkyrie/persistence/fedora/ordered_list.rb +90 -90
  25. data/lib/valkyrie/persistence/fedora/ordered_reader.rb +5 -5
  26. data/lib/valkyrie/persistence/fedora/permissive_schema.rb +3 -3
  27. data/lib/valkyrie/persistence/fedora/persister.rb +82 -83
  28. data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +15 -15
  29. data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +38 -18
  30. data/lib/valkyrie/persistence/fedora/query_service.rb +55 -54
  31. data/lib/valkyrie/persistence/memory/persister.rb +33 -33
  32. data/lib/valkyrie/persistence/memory/query_service.rb +52 -34
  33. data/lib/valkyrie/persistence/postgres/orm_converter.rb +52 -52
  34. data/lib/valkyrie/persistence/postgres/query_service.rb +86 -33
  35. data/lib/valkyrie/persistence/postgres/resource_converter.rb +1 -1
  36. data/lib/valkyrie/persistence/shared/json_value_mapper.rb +1 -1
  37. data/lib/valkyrie/persistence/solr/model_converter.rb +337 -337
  38. data/lib/valkyrie/persistence/solr/orm_converter.rb +3 -3
  39. data/lib/valkyrie/persistence/solr/persister.rb +4 -17
  40. data/lib/valkyrie/persistence/solr/queries/find_all_query.rb +6 -0
  41. data/lib/valkyrie/persistence/solr/queries/find_by_alternate_identifier_query.rb +1 -1
  42. data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +1 -1
  43. data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +1 -1
  44. data/lib/valkyrie/persistence/solr/query_service.rb +42 -53
  45. data/lib/valkyrie/persistence/solr/repository.rb +2 -1
  46. data/lib/valkyrie/rdf_patches.rb +2 -2
  47. data/lib/valkyrie/resource.rb +36 -5
  48. data/lib/valkyrie/specs/shared_specs/change_set.rb +1 -1
  49. data/lib/valkyrie/specs/shared_specs/persister.rb +13 -5
  50. data/lib/valkyrie/specs/shared_specs/queries.rb +112 -9
  51. data/lib/valkyrie/specs/shared_specs/resource.rb +1 -1
  52. data/lib/valkyrie/storage/fedora.rb +18 -18
  53. data/lib/valkyrie/storage_adapter.rb +16 -13
  54. data/lib/valkyrie/types.rb +3 -1
  55. data/lib/valkyrie/version.rb +1 -1
  56. data/lib/valkyrie/vocab/pcdm_use.rb +12 -0
  57. data/solr/config/solrconfig.xml +0 -10
  58. data/tasks/dev.rake +14 -51
  59. data/valkyrie.gemspec +4 -4
  60. metadata +38 -35
  61. data/.docker-stack/valkyrie-development/docker-compose.yml +0 -53
  62. data/.docker-stack/valkyrie-test/docker-compose.yml +0 -53
  63. data/db/seeds.rb +0 -8
  64. data/tasks/docker.rake +0 -31
@@ -48,7 +48,7 @@ module Valkyrie::Persistence::Solr
48
48
  # Construct a Time object from the datestamp for the resource creation date indexed in Solr
49
49
  # @return [Time]
50
50
  def created_at
51
- DateTime.parse(solr_document.fetch("created_at_dtsi").to_s).utc
51
+ DateTime.parse(solr_document.fetch("created_at_dtsi").to_s).new_offset(0)
52
52
  end
53
53
 
54
54
  # Construct a Time object from the datestamp for the date of the last resource update indexed in Solr
@@ -445,7 +445,7 @@ module Valkyrie::Persistence::Solr
445
445
  # @return [Boolean]
446
446
  def self.handles?(value)
447
447
  return false unless value.to_s.start_with?("datetime-")
448
- DateTime.iso8601(value.sub(/^datetime-/, '')).utc
448
+ DateTime.iso8601(value.sub(/^datetime-/, '')).new_offset(0)
449
449
  rescue
450
450
  false
451
451
  end
@@ -453,7 +453,7 @@ module Valkyrie::Persistence::Solr
453
453
  # Parses and casts the Solr field value into a UTC DateTime value
454
454
  # @return [Time]
455
455
  def result
456
- DateTime.parse(value.sub(/^datetime-/, '')).utc
456
+ DateTime.parse(value.sub(/^datetime-/, '')).new_offset(0)
457
457
  end
458
458
  end
459
459
  end
@@ -14,35 +14,22 @@ module Valkyrie::Persistence::Solr
14
14
  @adapter = adapter
15
15
  end
16
16
 
17
- # Persists a Valkyrie Resource into a Solr index
18
- # @note Fields are saved using Solr's dynamic fields functionality.
19
- # If the text has length > 1000, it is stored as *_tsim
20
- # otherwise it's stored as *_tsim, *_ssim, and *_tesim
21
- # e.g., a field called 'title' would be stored as 3 solr fields:
22
- # 'title_tsim'
23
- # 'title_ssim'
24
- # 'title_tesim'
25
- # @param [Valkyrie::Resource] resource
26
- # @return [Valkyrie::Resource] the persisted resource
17
+ # (see Valkyrie::Persistence::Memory::Persister#save)
27
18
  def save(resource:)
28
19
  repository([resource]).persist.first
29
20
  end
30
21
 
31
- # Persists a set of Valkyrie Resources into a Solr index
32
- # @param [Array<Valkyrie::Resource>] resources
33
- # @return [Valkyrie::Resource] the set of persisted resources
22
+ # (see Valkyrie::Persistence::Memory::Persister#save_all)
34
23
  def save_all(resources:)
35
24
  repository(resources).persist
36
25
  end
37
26
 
38
- # Deletes a Valkyrie Resource persisted into a Solr index
39
- # @param [Valkyrie::Resource] resource
40
- # @return [Valkyrie::Resource] the deleted resource
27
+ # (see Valkyrie::Persistence::Memory::Persister#delete)
41
28
  def delete(resource:)
42
29
  repository([resource]).delete.first
43
30
  end
44
31
 
45
- # Delete the Solr index of all Documents
32
+ # (see Valkyrie::Persistence::Memory::Persister#wipe!)
46
33
  def wipe!
47
34
  connection.delete_by_query("*:*")
48
35
  connection.commit
@@ -33,6 +33,12 @@ module Valkyrie::Persistence::Solr::Queries
33
33
  end
34
34
  end
35
35
 
36
+ # Queries without making Resrouces and returns the RSolr page_total value
37
+ # @return [Integer]
38
+ def count
39
+ connection.get("select", params: { q: query })["response"]["numFound"].to_s.to_i
40
+ end
41
+
36
42
  # Generates the Solr query for retrieving all Documents in the index
37
43
  # If a model is specified for the query, it is scoped to that Valkyrie resource type
38
44
  # @return [String]
@@ -32,7 +32,7 @@ module Valkyrie::Persistence::Solr::Queries
32
32
  # @note the field used here is alternate_ids_ssim and the value is prefixed by "id-"
33
33
  # @return [Hash]
34
34
  def resource
35
- connection.get("select", params: { q: "alternate_ids_ssim:\"id-#{alternate_identifier}\"", fl: "*", rows: 1 })["response"]["docs"].first
35
+ @resource ||= connection.get("select", params: { q: "alternate_ids_ssim:\"id-#{alternate_identifier}\"", fl: "*", rows: 1 })["response"]["docs"].first
36
36
  end
37
37
  end
38
38
  end
@@ -31,7 +31,7 @@ module Valkyrie::Persistence::Solr::Queries
31
31
  # Query Solr for for the first document with the ID in a field
32
32
  # @return [Hash]
33
33
  def resource
34
- connection.get("select", params: { q: "id:\"#{id}\"", fl: "*", rows: 1 })["response"]["docs"].first
34
+ @resource ||= connection.get("select", params: { q: "id:\"#{id}\"", fl: "*", rows: 1 })["response"]["docs"].first
35
35
  end
36
36
  end
37
37
  end
@@ -27,7 +27,7 @@ module Valkyrie::Persistence::Solr::Queries
27
27
  # Results are ordered by the member IDs specified in the Valkyrie Resource attribute
28
28
  # @yield [Valkyrie::Resource]
29
29
  def each
30
- return [] unless resource.id.present?
30
+ return [] if resource.id.blank?
31
31
  member_ids.map { |id| unordered_members.find { |member| member.id == id } }.reject(&:nil?).each do |member|
32
32
  yield member
33
33
  end
@@ -12,27 +12,21 @@ module Valkyrie::Persistence::Solr
12
12
  @adapter = adapter
13
13
  end
14
14
 
15
- # Find resources by Valkyrie ID
16
- # @param [Valkyrie::ID] id
17
- # @return [Valkyrie::Resource]
15
+ # (see Valkyrie::Persistence::Memory::QueryService#find_by)
18
16
  def find_by(id:)
19
17
  id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
20
18
  validate_id(id)
21
19
  Valkyrie::Persistence::Solr::Queries::FindByIdQuery.new(id, connection: connection, resource_factory: resource_factory).run
22
20
  end
23
21
 
24
- # Find resources by a Valkyrie alternate identifier
25
- # @param [Valkyrie::ID] alternate_identifier
26
- # @return [Valkyrie::Resource]
22
+ # (see Valkyrie::Persistence::Memory::QueryService#find_by_alternate_identifier)
27
23
  def find_by_alternate_identifier(alternate_identifier:)
28
24
  alternate_identifier = Valkyrie::ID.new(alternate_identifier.to_s) if alternate_identifier.is_a?(String)
29
25
  validate_id(alternate_identifier)
30
26
  Valkyrie::Persistence::Solr::Queries::FindByAlternateIdentifierQuery.new(alternate_identifier, connection: connection, resource_factory: resource_factory).run
31
27
  end
32
28
 
33
- # Find resources using a set of Valkyrie IDs
34
- # @param [Array<Valkyrie::ID>] ids
35
- # @return [Array<Valkyrie::Resource>]
29
+ # (see Valkyrie::Persistence::Memory::QueryService#find_many_by_ids)
36
30
  def find_many_by_ids(ids:)
37
31
  ids.map! do |id|
38
32
  id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
@@ -42,29 +36,29 @@ module Valkyrie::Persistence::Solr
42
36
  Valkyrie::Persistence::Solr::Queries::FindManyByIdsQuery.new(ids, connection: connection, resource_factory: resource_factory).run
43
37
  end
44
38
 
45
- # Find all of the Valkyrie Resources persisted in the Solr index
46
- # @return [Array<Valkyrie::Resource>]
39
+ # (see Valkyrie::Persistence::Memory::QueryService#find_all)
47
40
  def find_all
48
41
  Valkyrie::Persistence::Solr::Queries::FindAllQuery.new(connection: connection, resource_factory: resource_factory).run
49
42
  end
50
43
 
51
- # Find all of the Valkyrie Resources of a model persisted in the Solr index
52
- # @param [Class, String] model the Valkyrie::Resource Class
53
- # @return [Array<Valkyrie::Resource>]
44
+ # (see Valkyrie::Persistence::Memory::QueryService#find_all_of_model)
54
45
  def find_all_of_model(model:)
55
46
  Valkyrie::Persistence::Solr::Queries::FindAllQuery.new(connection: connection, resource_factory: resource_factory, model: model).run
56
47
  end
57
48
 
58
- # Find all of the parent resources for a given Valkyrie Resource
59
- # @param [Valkyrie::Resource] member resource
60
- # @return [Array<Valkyrie::Resource>] parent resources
49
+ # Count all of the Valkyrie Resources of a model persisted in the Solr index
50
+ # @param [Class, String] model the Valkyrie::Resource Class
51
+ # @return integer
52
+ def count_all_of_model(model:)
53
+ Valkyrie::Persistence::Solr::Queries::FindAllQuery.new(connection: connection, resource_factory: resource_factory, model: model).count
54
+ end
55
+
56
+ # (see Valkyrie::Persistence::Memory::QueryService#find_parents)
61
57
  def find_parents(resource:)
62
58
  find_inverse_references_by(resource: resource, property: :member_ids)
63
59
  end
64
60
 
65
- # Find all of the member resources for a given Valkyrie Resource
66
- # @param [Valkyrie::Resource] parent resource
67
- # @return [Array<Valkyrie::Resource>] member resources
61
+ # (see Valkyrie::Persistence::Memory::QueryService#find_members)
68
62
  def find_members(resource:, model: nil)
69
63
  Valkyrie::Persistence::Solr::Queries::FindMembersQuery.new(
70
64
  resource: resource,
@@ -74,53 +68,48 @@ module Valkyrie::Persistence::Solr
74
68
  ).run
75
69
  end
76
70
 
77
- # Find all of the resources referenced by a given Valkyrie Resource using a specific property
78
- # @param [Valkyrie::Resource] resource
79
- # @param [Symbol, String] property
80
- # @return [Array<Valkyrie::Resource>] referenced resources
81
- def find_references_by(resource:, property:)
82
- if ordered_property?(resource: resource, property: property)
83
- Valkyrie::Persistence::Solr::Queries::FindOrderedReferencesQuery.new(resource: resource, property: property, connection: connection, resource_factory: resource_factory).run
84
- else
85
- Valkyrie::Persistence::Solr::Queries::FindReferencesQuery.new(resource: resource, property: property, connection: connection, resource_factory: resource_factory).run
86
- end
71
+ # (see Valkyrie::Persistence::Memory::QueryService#find_references_by)
72
+ def find_references_by(resource:, property:, model: nil)
73
+ result =
74
+ if ordered_property?(resource: resource, property: property)
75
+ Valkyrie::Persistence::Solr::Queries::FindOrderedReferencesQuery.new(resource: resource, property: property, connection: connection, resource_factory: resource_factory).run
76
+ else
77
+ Valkyrie::Persistence::Solr::Queries::FindReferencesQuery.new(resource: resource, property: property, connection: connection, resource_factory: resource_factory).run
78
+ end
79
+ return result unless model
80
+ result.select { |obj| obj.is_a?(model) }
87
81
  end
88
82
 
89
- # Find all of the resources referencing a given Valkyrie Resource using a specific property
90
- # (e. g. find all resources referencing a parent resource as a collection using the property "member_of_collections")
91
- # @param [Valkyrie::Resource] referenced resource
92
- # @param [Symbol, String] property
93
- # @return [Array<Valkyrie::Resource>] related resources
94
- def find_inverse_references_by(resource: nil, id: nil, property:)
83
+ # (see Valkyrie::Persistence::Memory::QueryService#find_inverse_references_by)
84
+ def find_inverse_references_by(resource: nil, id: nil, property:, model: nil)
95
85
  raise ArgumentError, "Provide resource or id" unless resource || id
96
86
  ensure_persisted(resource) if resource
97
87
  id ||= resource.id
98
- Valkyrie::Persistence::Solr::Queries::FindInverseReferencesQuery.new(id: id, property: property, connection: connection, resource_factory: resource_factory).run
88
+ result = Valkyrie::Persistence::Solr::Queries::FindInverseReferencesQuery.new(id: id, property: property, connection: connection, resource_factory: resource_factory).run
89
+ return result unless model
90
+ result.select { |obj| obj.is_a?(model) }
99
91
  end
100
92
 
101
- # Construct the Valkyrie::Persistence::CustomQueryContainer object using this query service
102
- # @return [Valkyrie::Persistence::CustomQueryContainer]
93
+ # (see Valkyrie::Persistence::Memory::QueryService#custom_queries)
103
94
  def custom_queries
104
95
  @custom_queries ||= ::Valkyrie::Persistence::CustomQueryContainer.new(query_service: self)
105
96
  end
106
97
 
107
98
  private
108
99
 
109
- # Determine whether or not a value is a Valkyrie ID
110
- # @param [Object] id
111
- # @return [Boolean]
112
- def validate_id(id)
113
- raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
114
- end
100
+ # (see Valkyrie::Persistence::Memory::QueryService#validate_id)
101
+ def validate_id(id)
102
+ raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
103
+ end
115
104
 
116
- # Ensure that a given Valkyrie Resource has been persisted
117
- # @param [Valkyrie::Resource] resource
118
- def ensure_persisted(resource)
119
- raise ArgumentError, 'resource is not saved' unless resource.persisted?
120
- end
105
+ # (see Valkyrie::Persistence::Memory::QueryService#ensure_persisted)
106
+ def ensure_persisted(resource)
107
+ raise ArgumentError, 'resource is not saved' unless resource.persisted?
108
+ end
121
109
 
122
- def ordered_property?(resource:, property:)
123
- resource.ordered_attribute?(property)
124
- end
110
+ # (see Valkyrie::Persistence::Memory::QueryService#ordered_property?)
111
+ def ordered_property?(resource:, property:)
112
+ resource.ordered_attribute?(property)
113
+ end
125
114
  end
126
115
  end
@@ -62,8 +62,9 @@ module Valkyrie::Persistence::Solr
62
62
  # Given a new Valkyrie Resource, generate a random UUID and assign it to the Resource
63
63
  # @param [Valkyrie::Resource] resource
64
64
  # @param [String] the UUID for the new resource
65
+ # @see Valkyrie::Logging for details concerning log suppression.
65
66
  def generate_id(resource)
66
- Valkyrie.logger.warn "The Solr adapter is not meant to persist new resources, but is now generating an ID."
67
+ Valkyrie.logger.warn("The Solr adapter is not meant to persist new resources, but is now generating an ID.", logging_context: "Valkyrie::Persistence::Solr::Repository#generate_id")
67
68
  resource.id = SecureRandom.uuid
68
69
  end
69
70
 
@@ -6,12 +6,12 @@
6
6
  module RDF
7
7
  class Literal
8
8
  def as_json(*_args)
9
- JSON::LD::API.fromRdf([RDF::Statement.new(RDF::URI(""), RDF::URI(""), self)])[0][""][0]
9
+ ::JSON::LD::API.fromRdf([RDF::Statement.new(RDF::URI(""), RDF::URI(""), self)])[0][""][0]
10
10
  end
11
11
  end
12
12
  class URI
13
13
  def as_json(*_args)
14
- JSON::LD::API.fromRdf([RDF::Statement.new(RDF::URI(""), RDF::URI(""), self)])[0][""][0]
14
+ ::JSON::LD::API.fromRdf([RDF::Statement.new(RDF::URI(""), RDF::URI(""), self)])[0][""][0]
15
15
  end
16
16
  end
17
17
  end
@@ -13,6 +13,7 @@ module Valkyrie
13
13
  # @see https://github.com/samvera-labs/valkyrie/wiki/ChangeSets-and-Dirty-Tracking Validation and change tracking is provided by change sets
14
14
  #
15
15
  # @see lib/valkyrie/specs/shared_specs/resource.rb
16
+ # rubocop:disable Metrics/ClassLength
16
17
  class Resource < Dry::Struct
17
18
  include Draper::Decoratable
18
19
  # Allows a Valkyrie::Resource to be instantiated without providing every
@@ -48,13 +49,38 @@ module Valkyrie
48
49
  raise ReservedAttributeError, "#{name} is a reserved attribute and defined by Valkyrie::Resource, do not redefine it." if reserved_attributes.include?(name.to_sym) &&
49
50
  attribute_names.include?(name.to_sym) &&
50
51
  !internal
51
- define_method("#{name}=") do |value|
52
- set_value(name, value)
53
- end
54
- type = type.meta(ordered: true) if name == :member_ids
52
+
55
53
  super(name, type)
56
54
  end
57
55
 
56
+ # @param [Hash{Symbol => Dry::Types::Type}] new_schema
57
+ # @return [Dry::Struct]
58
+ # @raise [RepeatedAttributeError] when trying to define attribute with the
59
+ # same name as previously defined one
60
+ # @raise [ReservedAttributeError] when trying to define an attribute
61
+ # reserved by Valkyrie
62
+ # @see #attribute
63
+ # @note extends {Dry::Struct} by adding `#attr=` style setters
64
+ def self.attributes(new_schema)
65
+ new_schema[:member_ids] = new_schema[:member_ids].meta(ordered: true) if
66
+ new_schema.key?(:member_ids)
67
+
68
+ super
69
+
70
+ new_schema.each_key do |key|
71
+ key = key.to_s.chomp('?')
72
+ next if instance_methods.include?("#{key}=".to_sym)
73
+
74
+ class_eval(<<-RUBY)
75
+ def #{key}=(value)
76
+ set_value("#{key}".to_sym, value)
77
+ end
78
+ RUBY
79
+ end
80
+
81
+ self
82
+ end
83
+
58
84
  def self.reserved_attributes
59
85
  [:id, :internal_resource, :created_at, :updated_at, :new_record]
60
86
  end
@@ -87,6 +113,10 @@ module Valkyrie
87
113
  self.class.optimistic_locking_enabled?
88
114
  end
89
115
 
116
+ def clear_optimistic_lock_token!
117
+ send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", []) if optimistic_locking_enabled?
118
+ end
119
+
90
120
  def attributes
91
121
  Hash[self.class.attribute_names.map { |x| [x, nil] }].merge(super).freeze
92
122
  end
@@ -161,9 +191,10 @@ module Valkyrie
161
191
 
162
192
  # Returns if an attribute is set as ordered.
163
193
  def ordered_attribute?(key)
164
- self.class.schema.key(key).type.meta.try(:[], :ordered)
194
+ self.class.schema.key(key.to_sym).type.meta.try(:[], :ordered)
165
195
  end
166
196
 
167
197
  class ReservedAttributeError < StandardError; end
168
198
  end
199
+ # rubocop:enable Metrics/ClassLength
169
200
  end
@@ -75,7 +75,7 @@ RSpec.shared_examples 'a Valkyrie::ChangeSet' do |*_flags|
75
75
 
76
76
  describe "#optimistic_locking_enabled?" do
77
77
  it "delegates down to the resource" do
78
- expect(change_set.optimistic_locking_enabled?).to eq false
78
+ expect(change_set.optimistic_locking_enabled?).to eq change_set.resource.optimistic_locking_enabled?
79
79
  end
80
80
  end
81
81
  end
@@ -56,6 +56,14 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
56
56
  output = persister.save(resource: resource)
57
57
 
58
58
  expect(output.single_value).to eq "A single value"
59
+
60
+ reloaded = query_service.find_by(id: output.id)
61
+
62
+ reloaded.single_value = nil
63
+ persister.save(resource: reloaded)
64
+ reloaded = query_service.find_by(id: reloaded.id)
65
+
66
+ expect(reloaded.single_value).to eq nil
59
67
  end
60
68
 
61
69
  it "returns nil for an unset single value" do
@@ -76,14 +84,14 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
76
84
 
77
85
  it "can support deep nesting of resources" do
78
86
  pending "No support for deep nesting." if flags.include?(:no_deep_nesting)
79
- book = resource_class.new(title: "Sub-nested", author: [Valkyrie::ID.new("test"), RDF::Literal.new("Test", language: :fr), RDF::URI("http://test.com")])
87
+ book = resource_class.new(title: "Sub-nested", author: [Valkyrie::ID.new("test"), RDF::Literal.new("Test", language: :fr), RDF::URI("http://example.com")])
80
88
  book2 = resource_class.new(title: "Nested", nested_resource: book)
81
89
  book3 = persister.save(resource: resource_class.new(nested_resource: book2))
82
90
 
83
91
  reloaded = query_service.find_by(id: book3.id)
84
92
  expect(reloaded.nested_resource.first.title).to eq ["Nested"]
85
93
  expect(reloaded.nested_resource.first.nested_resource.first.title).to eq ["Sub-nested"]
86
- expect(reloaded.nested_resource.first.nested_resource.first.author).to contain_exactly Valkyrie::ID.new("test"), RDF::Literal.new("Test", language: :fr), RDF::URI("http://test.com")
94
+ expect(reloaded.nested_resource.first.nested_resource.first.author).to contain_exactly Valkyrie::ID.new("test"), RDF::Literal.new("Test", language: :fr), RDF::URI("http://example.com")
87
95
  end
88
96
 
89
97
  it "stores created_at/updated_at" do
@@ -186,9 +194,9 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
186
194
  reloaded = query_service.find_by(id: book.id)
187
195
 
188
196
  expect(reloaded.title.first.to_i).to eq(time1.to_i)
189
- expect(reloaded.title.first.zone).to eq('UTC')
197
+ expect(reloaded.title.first.zone).to eq('+00:00')
190
198
  expect(reloaded.author.first.to_i).to eq(time2.to_i)
191
- expect(reloaded.author.first.zone).to eq('UTC')
199
+ expect(reloaded.author.first.zone).to eq('+00:00')
192
200
  expect(reloaded.other_author.first).to eq "2019-01"
193
201
  end
194
202
 
@@ -352,7 +360,7 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
352
360
  resource = MyLockingResource.new(title: ["My Locked Resource"])
353
361
  initial_resource = persister.save(resource: resource)
354
362
  initial_token = initial_resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].first
355
- initial_resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", [])
363
+ initial_resource.clear_optimistic_lock_token!
356
364
  updated_resource = persister.save(resource: initial_resource)
357
365
  expect(initial_token.serialize)
358
366
  .not_to eq(updated_resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].first.serialize)
@@ -12,10 +12,15 @@ RSpec.shared_examples 'a Valkyrie query provider' do
12
12
  end
13
13
  class Valkyrie::Specs::SecondResource < Valkyrie::Resource
14
14
  end
15
+ class Valkyrie::Specs::ThirdResource < Valkyrie::Resource
16
+ attribute :a_member_of, Valkyrie::Types::Array
17
+ attribute :an_ordered_member_of, Valkyrie::Types::Array.meta(ordered: true)
18
+ end
15
19
  end
16
20
  after do
17
21
  Valkyrie::Specs.send(:remove_const, :CustomResource)
18
22
  Valkyrie::Specs.send(:remove_const, :SecondResource)
23
+ Valkyrie::Specs.send(:remove_const, :ThirdResource)
19
24
  end
20
25
  let(:resource_class) { Valkyrie::Specs::CustomResource }
21
26
  let(:query_service) { adapter.query_service } unless defined? query_service
@@ -28,10 +33,11 @@ RSpec.shared_examples 'a Valkyrie query provider' do
28
33
  it { is_expected.to respond_to(:find_by_alternate_identifier).with_keywords(:alternate_identifier) }
29
34
  it { is_expected.to respond_to(:find_many_by_ids).with_keywords(:ids) }
30
35
  it { is_expected.to respond_to(:find_members).with_keywords(:resource, :model) }
31
- it { is_expected.to respond_to(:find_references_by).with_keywords(:resource, :property) }
32
- it { is_expected.to respond_to(:find_inverse_references_by).with_keywords(:resource, :property) }
33
- it { is_expected.to respond_to(:find_inverse_references_by).with_keywords(:id, :property) }
36
+ it { is_expected.to respond_to(:find_references_by).with_keywords(:resource, :property, :model) }
37
+ it { is_expected.to respond_to(:find_inverse_references_by).with_keywords(:resource, :property, :model) }
38
+ it { is_expected.to respond_to(:find_inverse_references_by).with_keywords(:id, :property, :model) }
34
39
  it { is_expected.to respond_to(:find_parents).with_keywords(:resource) }
40
+ it { is_expected.to respond_to(:count_all_of_model).with_keywords(:model) }
35
41
 
36
42
  describe ".find_all" do
37
43
  it "returns all created resources" do
@@ -273,14 +279,50 @@ RSpec.shared_examples 'a Valkyrie query provider' do
273
279
  expect(query_service.find_references_by(resource: child, property: :an_ordered_member_of).map(&:id).to_a).to eq []
274
280
  end
275
281
  end
282
+
283
+ context "filtering by model" do
284
+ context "when the object has related resources that match the filter" do
285
+ subject { query_service.find_references_by(resource: child1, property: :a_member_of, model: Valkyrie::Specs::SecondResource) }
286
+ let(:child1) { persister.save(resource: Valkyrie::Specs::ThirdResource.new(a_member_of: [parent3.id, parent2.id, parent.id])) }
287
+ let(:parent) { persister.save(resource: Valkyrie::Specs::SecondResource.new) }
288
+ let(:parent2) { persister.save(resource: Valkyrie::Specs::CustomResource.new) }
289
+ let(:parent3) { persister.save(resource: Valkyrie::Specs::SecondResource.new) }
290
+
291
+ it "returns only resources with the relationship filtered to the specified model" do
292
+ expect(subject.map(&:id).to_a).to match_array [parent3.id, parent.id]
293
+ end
294
+ end
295
+
296
+ context "when the object has ordered related resources that match the filter" do
297
+ subject { query_service.find_references_by(resource: child1, property: :an_ordered_member_of, model: Valkyrie::Specs::SecondResource) }
298
+ let(:child1) { persister.save(resource: Valkyrie::Specs::ThirdResource.new(an_ordered_member_of: [parent.id, parent3.id, parent2.id, parent.id])) }
299
+ let(:parent) { persister.save(resource: Valkyrie::Specs::SecondResource.new) }
300
+ let(:parent2) { persister.save(resource: Valkyrie::Specs::CustomResource.new) }
301
+ let(:parent3) { persister.save(resource: Valkyrie::Specs::SecondResource.new) }
302
+
303
+ it "returns only resources with the relationship filtered to the specified model" do
304
+ expect(subject.map(&:id).to_a).to match_array [parent.id, parent3.id, parent.id]
305
+ end
306
+ end
307
+
308
+ context "when there are no related resources that match the filter" do
309
+ subject { query_service.find_references_by(resource: child1, property: :a_member_of, model: Valkyrie::Specs::SecondResource) }
310
+ let(:child1) { persister.save(resource: Valkyrie::Specs::ThirdResource.new(a_member_of: [parent.id])) }
311
+ let(:parent) { persister.save(resource: Valkyrie::Specs::CustomResource.new) }
312
+
313
+ it "returns an empty array" do
314
+ expect(subject.to_a).to eq []
315
+ end
316
+ end
317
+ end
276
318
  end
277
319
 
278
320
  describe ".find_inverse_references_by" do
279
321
  context "when the resource is saved" do
280
322
  context "when the property is unordered" do
281
323
  it "returns everything which references the given resource by the given property" do
282
- parent = persister.save(resource: resource_class.new)
283
- parent2 = persister.save(resource: resource_class.new)
324
+ parent = persister.save(resource: Valkyrie::Specs::SecondResource.new)
325
+ parent2 = persister.save(resource: Valkyrie::Specs::SecondResource.new)
284
326
  child = persister.save(resource: resource_class.new(a_member_of: [parent.id]))
285
327
  child2 = persister.save(resource: resource_class.new(a_member_of: [parent.id, parent2.id, parent.id]))
286
328
  persister.save(resource: resource_class.new)
@@ -290,7 +332,7 @@ RSpec.shared_examples 'a Valkyrie query provider' do
290
332
  end
291
333
 
292
334
  it "returns an empty array if there are none" do
293
- parent = persister.save(resource: resource_class.new)
335
+ parent = persister.save(resource: Valkyrie::Specs::SecondResource.new)
294
336
 
295
337
  expect(query_service.find_inverse_references_by(resource: parent, property: :a_member_of).to_a).to eq []
296
338
  end
@@ -298,7 +340,7 @@ RSpec.shared_examples 'a Valkyrie query provider' do
298
340
 
299
341
  context "when the property is ordered" do
300
342
  it "returns everything which references the given resource by the given property" do
301
- parent = persister.save(resource: resource_class.new)
343
+ parent = persister.save(resource: Valkyrie::Specs::SecondResource.new)
302
344
  child = persister.save(resource: resource_class.new(an_ordered_member_of: [parent.id]))
303
345
  child2 = persister.save(resource: resource_class.new(an_ordered_member_of: [parent.id, parent.id]))
304
346
  persister.save(resource: resource_class.new)
@@ -307,12 +349,38 @@ RSpec.shared_examples 'a Valkyrie query provider' do
307
349
  expect(query_service.find_inverse_references_by(resource: parent, property: :an_ordered_member_of).map(&:id).to_a).to contain_exactly child.id, child2.id
308
350
  end
309
351
  end
352
+
353
+ context "when the property is ordered for one child but not the other" do
354
+ before do
355
+ class Valkyrie::Specs::Parent < Valkyrie::Resource; end
356
+ class Valkyrie::Specs::ChildWithUnorderedParents < Valkyrie::Resource
357
+ attribute :a_member_of, Valkyrie::Types::Array
358
+ end
359
+ class Valkyrie::Specs::ChildWithOrderedParents < Valkyrie::Resource
360
+ attribute :a_member_of, Valkyrie::Types::Array.meta(ordered: true)
361
+ end
362
+ end
363
+ after do
364
+ Valkyrie::Specs.send(:remove_const, :Parent)
365
+ Valkyrie::Specs.send(:remove_const, :ChildWithUnorderedParents)
366
+ Valkyrie::Specs.send(:remove_const, :ChildWithOrderedParents)
367
+ end
368
+ it "returns" do
369
+ parent = persister.save(resource: Valkyrie::Specs::Parent.new)
370
+ child = persister.save(resource: Valkyrie::Specs::ChildWithUnorderedParents.new(a_member_of: [parent.id]))
371
+ child2 = persister.save(resource: Valkyrie::Specs::ChildWithOrderedParents.new(a_member_of: [parent.id, parent.id]))
372
+ persister.save(resource: Valkyrie::Specs::ChildWithUnorderedParents.new)
373
+ persister.save(resource: Valkyrie::Specs::Parent.new)
374
+
375
+ expect(query_service.find_inverse_references_by(resource: parent, property: :a_member_of).map(&:id).to_a).to contain_exactly child.id, child2.id
376
+ end
377
+ end
310
378
  end
311
379
 
312
380
  context "when id is passed instead of resource" do
313
381
  it "returns everything which references the given resource by the given property" do
314
- parent = persister.save(resource: resource_class.new)
315
- parent2 = persister.save(resource: resource_class.new)
382
+ parent = persister.save(resource: Valkyrie::Specs::SecondResource.new)
383
+ parent2 = persister.save(resource: Valkyrie::Specs::SecondResource.new)
316
384
  child = persister.save(resource: resource_class.new(a_member_of: [parent.id]))
317
385
  child2 = persister.save(resource: resource_class.new(a_member_of: [parent.id, parent2.id, parent.id]))
318
386
  persister.save(resource: resource_class.new)
@@ -335,6 +403,32 @@ RSpec.shared_examples 'a Valkyrie query provider' do
335
403
  expect { query_service.find_inverse_references_by(resource: parent, property: :a_member_of).to_a }.to raise_error ArgumentError
336
404
  end
337
405
  end
406
+
407
+ context "filtering by model" do
408
+ subject { query_service.find_inverse_references_by(resource: parent, property: :a_member_of, model: Valkyrie::Specs::CustomResource) }
409
+
410
+ context "when the object has related resources that match the filter" do
411
+ let(:parent) { persister.save(resource: Valkyrie::Specs::SecondResource.new) }
412
+
413
+ it "returns only resources with the relationship filtered to the specified model" do
414
+ child1 = persister.save(resource: Valkyrie::Specs::CustomResource.new(a_member_of: [parent.id]))
415
+ persister.save(resource: Valkyrie::Specs::ThirdResource.new(a_member_of: [parent.id]))
416
+ child3 = persister.save(resource: Valkyrie::Specs::CustomResource.new(a_member_of: [parent.id]))
417
+
418
+ expect(subject.map(&:id).to_a).to match_array [child3.id, child1.id]
419
+ end
420
+ end
421
+
422
+ context "when there are no related resources that match the filter" do
423
+ let(:parent) { persister.save(resource: Valkyrie::Specs::SecondResource.new) }
424
+
425
+ it "returns an empty array" do
426
+ persister.save(resource: Valkyrie::Specs::ThirdResource.new(a_member_of: [parent.id]))
427
+
428
+ expect(subject.to_a).to eq []
429
+ end
430
+ end
431
+ end
338
432
  end
339
433
 
340
434
  describe ".find_parents" do
@@ -411,4 +505,13 @@ RSpec.shared_examples 'a Valkyrie query provider' do
411
505
  expect(resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]).not_to be_empty
412
506
  end
413
507
  end
508
+
509
+ describe ".count_all_of_model" do
510
+ it "counts all of that model" do
511
+ persister.save(resource: resource_class.new)
512
+ persister.save(resource: Valkyrie::Specs::SecondResource.new)
513
+ persister.save(resource: Valkyrie::Specs::SecondResource.new)
514
+ expect(query_service.count_all_of_model(model: Valkyrie::Specs::SecondResource)).to eq(2)
515
+ end
516
+ end
414
517
  end