valkyrie 1.2.0.rc1 → 1.2.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +12 -4
  4. data/lib/valkyrie/persistence/composite_persister.rb +1 -1
  5. data/lib/valkyrie/persistence/fedora/list_node.rb +42 -3
  6. data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +26 -0
  7. data/lib/valkyrie/persistence/fedora/ordered_list.rb +36 -5
  8. data/lib/valkyrie/persistence/fedora/ordered_reader.rb +6 -0
  9. data/lib/valkyrie/persistence/fedora/permissive_schema.rb +20 -1
  10. data/lib/valkyrie/persistence/fedora/persister.rb +33 -4
  11. data/lib/valkyrie/persistence/fedora/persister/alternate_identifier.rb +6 -0
  12. data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +254 -4
  13. data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +250 -3
  14. data/lib/valkyrie/persistence/fedora/persister/resource_factory.rb +6 -0
  15. data/lib/valkyrie/persistence/fedora/query_service.rb +22 -4
  16. data/lib/valkyrie/persistence/memory/metadata_adapter.rb +2 -0
  17. data/lib/valkyrie/persistence/memory/persister.rb +11 -3
  18. data/lib/valkyrie/persistence/memory/query_service.rb +11 -0
  19. data/lib/valkyrie/persistence/postgres/metadata_adapter.rb +2 -0
  20. data/lib/valkyrie/persistence/postgres/orm.rb +4 -0
  21. data/lib/valkyrie/persistence/postgres/orm_converter.rb +62 -2
  22. data/lib/valkyrie/persistence/postgres/persister.rb +18 -7
  23. data/lib/valkyrie/persistence/postgres/query_service.rb +103 -11
  24. data/lib/valkyrie/persistence/postgres/resource_converter.rb +10 -0
  25. data/lib/valkyrie/persistence/postgres/resource_factory.rb +3 -0
  26. data/lib/valkyrie/persistence/solr/composite_indexer.rb +10 -0
  27. data/lib/valkyrie/persistence/solr/metadata_adapter.rb +7 -0
  28. data/lib/valkyrie/persistence/solr/model_converter.rb +137 -0
  29. data/lib/valkyrie/persistence/solr/orm_converter.rb +168 -0
  30. data/lib/valkyrie/persistence/solr/persister.rb +13 -5
  31. data/lib/valkyrie/persistence/solr/queries.rb +1 -0
  32. data/lib/valkyrie/persistence/solr/queries/default_paginator.rb +11 -1
  33. data/lib/valkyrie/persistence/solr/queries/find_all_query.rb +12 -0
  34. data/lib/valkyrie/persistence/solr/queries/find_by_alternate_identifier_query.rb +12 -0
  35. data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +11 -0
  36. data/lib/valkyrie/persistence/solr/queries/find_inverse_references_query.rb +13 -0
  37. data/lib/valkyrie/persistence/solr/queries/find_many_by_ids_query.rb +9 -0
  38. data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +23 -0
  39. data/lib/valkyrie/persistence/solr/queries/find_ordered_references_query.rb +50 -0
  40. data/lib/valkyrie/persistence/solr/queries/find_references_query.rb +15 -0
  41. data/lib/valkyrie/persistence/solr/query_service.rb +47 -14
  42. data/lib/valkyrie/persistence/solr/repository.rb +21 -4
  43. data/lib/valkyrie/persistence/solr/resource_factory.rb +2 -0
  44. data/lib/valkyrie/resource.rb +1 -0
  45. data/lib/valkyrie/specs/shared_specs.rb +1 -0
  46. data/lib/valkyrie/specs/shared_specs/persister.rb +92 -2
  47. data/lib/valkyrie/specs/shared_specs/queries.rb +12 -0
  48. data/lib/valkyrie/specs/shared_specs/solr_indexer.rb +40 -0
  49. data/lib/valkyrie/storage/fedora.rb +0 -2
  50. data/lib/valkyrie/version.rb +1 -1
  51. metadata +4 -2
@@ -7,21 +7,29 @@ module Valkyrie::Persistence::Postgres
7
7
  attr_reader :adapter
8
8
  delegate :resource_factory, to: :adapter
9
9
 
10
- # @note (see Valkyrie::Persistence::Memory::Persister#initialize)
10
+ # @param [MetadataAdapter] adapter
11
11
  def initialize(adapter:)
12
12
  @adapter = adapter
13
13
  end
14
14
 
15
- # (see Valkyrie::Persistence::Memory::Persister#save)
15
+ # Persists a resource within the database
16
+ # @param [Valkyrie::Resource] resource
17
+ # @return [Valkyrie::Resource] the persisted/updated resource
18
+ # @raise [Valkyrie::Persistence::StaleObjectError] raised if the resource
19
+ # was modified in the database between been read into memory and persisted
16
20
  def save(resource:)
17
21
  orm_object = resource_factory.from_resource(resource: resource)
18
22
  orm_object.save!
19
23
  resource_factory.to_resource(object: orm_object)
20
24
  rescue ActiveRecord::StaleObjectError
21
- raise Valkyrie::Persistence::StaleObjectError, resource.id.to_s
25
+ raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process."
22
26
  end
23
27
 
24
- # (see Valkyrie::Persistence::Memory::Persister#save_all)
28
+ # Persists a set of resources within the database
29
+ # @param [Array<Valkyrie::Resource>] resources
30
+ # @return [Array<Valkyrie::Resource>] the persisted/updated resources
31
+ # @raise [Valkyrie::Persistence::StaleObjectError] raised if the resource
32
+ # was modified in the database between been read into memory and persisted
25
33
  def save_all(resources:)
26
34
  resource_factory.orm_class.transaction do
27
35
  resources.map do |resource|
@@ -29,17 +37,20 @@ module Valkyrie::Persistence::Postgres
29
37
  end
30
38
  end
31
39
  rescue Valkyrie::Persistence::StaleObjectError
32
- raise Valkyrie::Persistence::StaleObjectError
40
+ raise Valkyrie::Persistence::StaleObjectError, "One or more resources have been updated by another process."
33
41
  end
34
42
 
35
- # (see Valkyrie::Persistence::Memory::Persister#delete)
43
+ # Deletes a resource persisted within the database
44
+ # @param [Valkyrie::Resource] resource
45
+ # @return [Valkyrie::Resource] the deleted resource
36
46
  def delete(resource:)
37
47
  orm_object = resource_factory.from_resource(resource: resource)
38
48
  orm_object.delete
39
49
  resource
40
50
  end
41
51
 
42
- # (see Valkyrie::Persistence::Memory::Persister#wipe!)
52
+ # Deletes all resources of a specific Valkyrie Resource type persisted in
53
+ # the database
43
54
  def wipe!
44
55
  resource_factory.orm_class.delete_all
45
56
  end
@@ -10,26 +10,34 @@ module Valkyrie::Persistence::Postgres
10
10
  attr_reader :resource_factory
11
11
  delegate :orm_class, to: :resource_factory
12
12
 
13
- # @note (see Valkyrie::Persistence::Memory::QueryService#initialize)
13
+ # @param [ResourceFactory] resource_factory
14
14
  def initialize(resource_factory:)
15
15
  @resource_factory = resource_factory
16
16
  end
17
17
 
18
- # (see Valkyrie::Persistence::Memory::QueryService#find_all)
18
+ # Retrieve all records for the resource and construct Valkyrie Resources
19
+ # for each record
20
+ # @return [Array<Valkyrie::Resource>]
19
21
  def find_all
20
22
  orm_class.all.lazy.map do |orm_object|
21
23
  resource_factory.to_resource(object: orm_object)
22
24
  end
23
25
  end
24
26
 
25
- # (see Valkyrie::Persistence::Memory::QueryService#find_all_of_model)
27
+ # Retrieve all records for a specific resource type and construct Valkyrie
28
+ # Resources for each record
29
+ # @param [Class] model
30
+ # @return [Array<Valkyrie::Resource>]
26
31
  def find_all_of_model(model:)
27
32
  orm_class.where(internal_resource: model.to_s).lazy.map do |orm_object|
28
33
  resource_factory.to_resource(object: orm_object)
29
34
  end
30
35
  end
31
36
 
32
- # (see Valkyrie::Persistence::Memory::QueryService#find_by)
37
+ # Find a record using a Valkyrie ID, and map it to a Valkyrie Resource
38
+ # @param [Valkyrie::ID, String] id
39
+ # @return [Valkyrie::Resource]
40
+ # @raise [Valkyrie::Persistence::ObjectNotFoundError]
33
41
  def find_by(id:)
34
42
  id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
35
43
  validate_id(id)
@@ -38,7 +46,10 @@ module Valkyrie::Persistence::Postgres
38
46
  raise Valkyrie::Persistence::ObjectNotFoundError
39
47
  end
40
48
 
41
- # (see Valkyrie::Persistence::Memory::QueryService#find_by_alternate_identifier)
49
+ # Find and a record using a Valkyrie ID for an alternate ID, and construct
50
+ # a Valkyrie Resource
51
+ # @param [Valkyrie::ID] alternate_identifier
52
+ # @return [Valkyrie::Resource]
42
53
  def find_by_alternate_identifier(alternate_identifier:)
43
54
  alternate_identifier = Valkyrie::ID.new(alternate_identifier.to_s) if alternate_identifier.is_a?(String)
44
55
  validate_id(alternate_identifier)
@@ -46,7 +57,10 @@ module Valkyrie::Persistence::Postgres
46
57
  run_query(find_inverse_references_query, internal_array).first || raise(Valkyrie::Persistence::ObjectNotFoundError)
47
58
  end
48
59
 
49
- # (see Valkyrie::Persistence::Memory::QueryService#find_many_by_ids)
60
+ # Find records using a set of Valkyrie IDs, and map each to Valkyrie
61
+ # Resources
62
+ # @param [Array<Valkyrie::ID>] ids
63
+ # @return [Array<Valkyrie::Resource>]
50
64
  def find_many_by_ids(ids:)
51
65
  ids.map! do |id|
52
66
  id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
@@ -59,7 +73,10 @@ module Valkyrie::Persistence::Postgres
59
73
  end
60
74
  end
61
75
 
62
- # (see Valkyrie::Persistence::Memory::QueryService#find_members)
76
+ # Find all member resources for a given Valkyrie Resource
77
+ # @param [Valkyrie::Resource] resource
78
+ # @param [Class] model
79
+ # @return [Array<Valkyrie::Resource>]
63
80
  def find_members(resource:, model: nil)
64
81
  return [] if resource.id.blank?
65
82
  if model
@@ -69,30 +86,56 @@ module Valkyrie::Persistence::Postgres
69
86
  end
70
87
  end
71
88
 
72
- # (see Valkyrie::Persistence::Memory::QueryService#find_parents)
89
+ # Find all parent resources for a given Valkyrie Resource
90
+ # @param [Valkyrie::Resource] resource
91
+ # @return [Array<Valkyrie::Resource>]
73
92
  def find_parents(resource:)
74
93
  find_inverse_references_by(resource: resource, property: :member_ids)
75
94
  end
76
95
 
77
- # (see Valkyrie::Persistence::Memory::QueryService#find_references_by)
96
+ # Find all resources related to a given Valkyrie Resource by a property
97
+ # @param [Valkyrie::Resource] resource
98
+ # @param [String] property
99
+ # @return [Array<Valkyrie::Resource>]
78
100
  def find_references_by(resource:, property:)
79
101
  return [] if resource.id.blank? || resource[property].blank?
80
- run_query(find_references_query, property, resource.id.to_s)
102
+ # only return ordered if needed to avoid performance penalties
103
+ if ordered_property?(resource: resource, property: property)
104
+ run_query(find_ordered_references_query, property, resource.id.to_s)
105
+ else
106
+ run_query(find_references_query, property, resource.id.to_s)
107
+ end
81
108
  end
82
109
 
83
- # (see Valkyrie::Persistence::Memory::QueryService#find_inverse_references_by)
110
+ # Find all resources referencing a given Valkyrie Resource by a property
111
+ # @param [Valkyrie::Resource] resource
112
+ # @param [String] property
113
+ # @return [Array<Valkyrie::Resource>]
84
114
  def find_inverse_references_by(resource:, property:)
85
115
  ensure_persisted(resource)
86
116
  internal_array = "{\"#{property}\": [{\"id\": \"#{resource.id}\"}]}"
87
117
  run_query(find_inverse_references_query, internal_array)
88
118
  end
89
119
 
120
+ # Execute a query in SQL for resource records and map them to Valkyrie
121
+ # Resources
122
+ # @param [String] query
123
+ # @return [Array<Valkyrie::Resource>]
90
124
  def run_query(query, *args)
91
125
  orm_class.find_by_sql(([query] + args)).lazy.map do |object|
92
126
  resource_factory.to_resource(object: object)
93
127
  end
94
128
  end
95
129
 
130
+ # Generate the SQL query for retrieving member resources in PostgreSQL using a
131
+ # resource ID as an argument.
132
+ # @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
133
+ # @note this uses a CROSS JOIN for all combinations of member IDs with the
134
+ # IDs of their parents
135
+ # @see https://www.postgresql.org/docs/current/static/queries-table-expressions.html#QUERIES-FROM
136
+ # This also uses JSON functions in order to retrieve JSON property values
137
+ # @see https://www.postgresql.org/docs/current/static/functions-json.html
138
+ # @return [String]
96
139
  def find_members_query
97
140
  <<-SQL
98
141
  SELECT member.* FROM orm_resources a,
@@ -102,6 +145,15 @@ module Valkyrie::Persistence::Postgres
102
145
  SQL
103
146
  end
104
147
 
148
+ # Generate the SQL query for retrieving member resources in PostgreSQL using a
149
+ # resource ID and resource type as arguments.
150
+ # @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
151
+ # @note this uses a CROSS JOIN for all combinations of member IDs with the
152
+ # IDs of their parents
153
+ # @see https://www.postgresql.org/docs/current/static/queries-table-expressions.html#QUERIES-FROM
154
+ # This also uses JSON functions in order to retrieve JSON property values
155
+ # @see https://www.postgresql.org/docs/current/static/functions-json.html
156
+ # @return [String]
105
157
  def find_members_with_type_query
106
158
  <<-SQL
107
159
  SELECT member.* FROM orm_resources a,
@@ -112,6 +164,12 @@ module Valkyrie::Persistence::Postgres
112
164
  SQL
113
165
  end
114
166
 
167
+ # Generate the SQL query for retrieving member resources in PostgreSQL using a
168
+ # JSON object literal as an argument (e. g. { "alternate_ids": [{"id": "d6e88f80-41b3-4dbf-a2a0-cd79e20f6d10"}] }).
169
+ # @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
170
+ # This uses JSON functions in order to retrieve JSON property values
171
+ # @see https://www.postgresql.org/docs/current/static/functions-json.html
172
+ # @return [String]
115
173
  def find_inverse_references_query
116
174
  <<-SQL
117
175
  SELECT * FROM orm_resources WHERE
@@ -119,6 +177,15 @@ module Valkyrie::Persistence::Postgres
119
177
  SQL
120
178
  end
121
179
 
180
+ # Generate the SQL query for retrieving member resources in PostgreSQL using a
181
+ # JSON object literal and resource ID as arguments.
182
+ # @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
183
+ # @note this uses a CROSS JOIN for all combinations of member IDs with the
184
+ # IDs of their parents
185
+ # @see https://www.postgresql.org/docs/current/static/queries-table-expressions.html#QUERIES-FROM
186
+ # This also uses JSON functions in order to retrieve JSON property values
187
+ # @see https://www.postgresql.org/docs/current/static/functions-json.html
188
+ # @return [String]
122
189
  def find_references_query
123
190
  <<-SQL
124
191
  SELECT member.* FROM orm_resources a,
@@ -127,22 +194,47 @@ module Valkyrie::Persistence::Postgres
127
194
  SQL
128
195
  end
129
196
 
197
+ def find_ordered_references_query
198
+ <<-SQL
199
+ SELECT member.* FROM orm_resources a,
200
+ jsonb_array_elements(a.metadata->?) WITH ORDINALITY AS b(member, member_pos)
201
+ JOIN orm_resources member ON (b.member->>'id')::#{id_type} = member.id WHERE a.id = ?
202
+ ORDER BY b.member_pos
203
+ SQL
204
+ end
205
+
206
+ # Constructs a Valkyrie::Persistence::CustomQueryContainer using this query service
207
+ # @return [Valkyrie::Persistence::CustomQueryContainer]
130
208
  def custom_queries
131
209
  @custom_queries ||= ::Valkyrie::Persistence::CustomQueryContainer.new(query_service: self)
132
210
  end
133
211
 
134
212
  private
135
213
 
214
+ # Determines whether or not an Object is a Valkyrie ID
215
+ # @param [Object] id
216
+ # @raise [ArgumentError]
136
217
  def validate_id(id)
137
218
  raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
138
219
  end
139
220
 
221
+ # Determines whether or not a resource has been persisted
222
+ # @param [Object] resource
223
+ # @raise [ArgumentError]
140
224
  def ensure_persisted(resource)
141
225
  raise ArgumentError, 'resource is not saved' unless resource.persisted?
142
226
  end
143
227
 
228
+ # Accesses the data type in PostgreSQL used for the primary key
229
+ # (For example, a UUID)
230
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaCache.html#method-i-columns_hash
231
+ # @return [Symbol]
144
232
  def id_type
145
233
  @id_type ||= orm_class.columns_hash["id"].type
146
234
  end
235
+
236
+ def ordered_property?(resource:, property:)
237
+ resource.class.schema[property].meta.try(:[], :ordered)
238
+ end
147
239
  end
148
240
  end
@@ -5,11 +5,16 @@ module Valkyrie::Persistence::Postgres
5
5
  class ResourceConverter
6
6
  delegate :orm_class, to: :resource_factory
7
7
  attr_reader :resource, :resource_factory
8
+
9
+ # @param [Valkyrie::Resource] resource
10
+ # @param [ResourceFactory] resource_factory
8
11
  def initialize(resource, resource_factory:)
9
12
  @resource = resource
10
13
  @resource_factory = resource_factory
11
14
  end
12
15
 
16
+ # Converts the Valkyrie Resource into an ActiveRecord object
17
+ # @return [ORM::Resource]
13
18
  def convert!
14
19
  orm_class.find_or_initialize_by(id: resource.id && resource.id.to_s).tap do |orm_object|
15
20
  orm_object.internal_resource = resource.internal_resource
@@ -18,6 +23,10 @@ module Valkyrie::Persistence::Postgres
18
23
  end
19
24
  end
20
25
 
26
+ # Retrieves the optimistic lock token from the Valkyrie attribute value and
27
+ # sets it to the lock_version on ORM resource
28
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
29
+ # @param [ORM::Resource] orm_object
21
30
  def process_lock_token(orm_object)
22
31
  return unless resource.respond_to?(Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK)
23
32
  postgres_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find do |token|
@@ -29,6 +38,7 @@ module Valkyrie::Persistence::Postgres
29
38
 
30
39
  # Convert attributes to all be arrays to better enable querying and
31
40
  # "changing of minds" later on.
41
+ # @return [Hash]
32
42
  def attributes
33
43
  Hash[
34
44
  resource.attributes.except(:id, :internal_resource, :created_at, :updated_at).compact.map do |k, v|
@@ -7,6 +7,8 @@ module Valkyrie::Persistence::Postgres
7
7
  class ResourceFactory
8
8
  attr_reader :adapter
9
9
  delegate :id, to: :adapter, prefix: true
10
+
11
+ # @param [MetadataAdapter] adapter
10
12
  def initialize(adapter:)
11
13
  @adapter = adapter
12
14
  end
@@ -27,6 +29,7 @@ module Valkyrie::Persistence::Postgres
27
29
 
28
30
  # Accessor for the ActiveRecord class which all Postgres resources are an
29
31
  # instance of.
32
+ # @return [Class]
30
33
  def orm_class
31
34
  ::Valkyrie::Persistence::Postgres::ORM::Resource
32
35
  end
@@ -6,21 +6,31 @@ module Valkyrie::Persistence::Solr
6
6
  # @see https://en.wikipedia.org/wiki/Composite_pattern
7
7
  class CompositeIndexer
8
8
  attr_reader :indexers
9
+
10
+ # @param [Array<Object>] indexers
9
11
  def initialize(*indexers)
10
12
  @indexers = indexers
11
13
  end
12
14
 
15
+ # Construct a new Instance object
16
+ # @param [Valkyrie::Resource] resource
13
17
  def new(resource:)
14
18
  Instance.new(indexers, resource: resource)
15
19
  end
16
20
 
21
+ # Class providing the common method interface for the Indexer
17
22
  class Instance
18
23
  attr_reader :indexers, :resource
24
+
25
+ # @param [Array<Object>] indexers
26
+ # @param [Valkyrie::Resource] resource
19
27
  def initialize(indexers, resource:)
20
28
  @resource = resource
21
29
  @indexers = indexers.map { |i| i.new(resource: resource) }
22
30
  end
23
31
 
32
+ # Generate the Solr Documents from the indexers
33
+ # @return [Array<Hash>]
24
34
  def to_solr
25
35
  indexers.map(&:to_solr).inject({}, &:merge)
26
36
  end
@@ -50,6 +50,9 @@ module Valkyrie::Persistence::Solr
50
50
  )
51
51
  end
52
52
 
53
+ # Generate the Valkyrie ID for this unique metadata adapter
54
+ # This uses the URL of the Solr endpoint to ensure that this is unique
55
+ # @return [Valkyrie::ID]
53
56
  def id
54
57
  @id ||= Valkyrie::ID.new(Digest::MD5.hexdigest(connection.base_uri.to_s))
55
58
  end
@@ -60,9 +63,13 @@ module Valkyrie::Persistence::Solr
60
63
  Valkyrie::Persistence::Solr::ResourceFactory.new(resource_indexer: resource_indexer, adapter: self)
61
64
  end
62
65
 
66
+ # Class modeling the indexer for cases where indexing is *not* performed
63
67
  class NullIndexer
68
+ # @note this is a no-op
64
69
  def initialize(_); end
65
70
 
71
+ # Generate the Solr hash
72
+ # @return [Hash] this will be empty
66
73
  def to_solr
67
74
  {}
68
75
  end
@@ -5,20 +5,31 @@ module Valkyrie::Persistence::Solr
5
5
  class ModelConverter
6
6
  attr_reader :resource, :resource_factory
7
7
  delegate :resource_indexer, to: :resource_factory
8
+
9
+ # @param [Valkyrie::Resource] resource
10
+ # @param [ResourceFactory] resource_factory
8
11
  def initialize(resource, resource_factory:)
9
12
  @resource = resource
10
13
  @resource_factory = resource_factory
11
14
  end
12
15
 
16
+ # Converts the Valkyrie Resource to the Solr Document
17
+ # @note this modifies the Solr Document for the conversion
18
+ # @return [Hash] the Solr Document for the Valkyrie Resource
13
19
  def convert!
20
+ # Appends the resource type to the Solr Document
14
21
  to_h.merge(Valkyrie::Persistence::Solr::Queries::MODEL.to_sym => [resource.internal_resource])
15
22
  .merge(indexer_solr(resource))
16
23
  end
17
24
 
25
+ # Generate the Solr Document for a Valkyrie Resource using the indexer
26
+ # @param [Valkyrie::Resource] resource
27
+ # @return [Hash] the Solr Document as a Hash
18
28
  def indexer_solr(resource)
19
29
  resource_indexer.new(resource: resource).to_solr
20
30
  end
21
31
 
32
+ # Access the ID for the Valkyrie Resource being converted to a Solr Document
22
33
  # @return [String] The solr document ID
23
34
  def id
24
35
  resource.id.to_s
@@ -45,6 +56,10 @@ module Valkyrie::Persistence::Solr
45
56
 
46
57
  private
47
58
 
59
+ # Maps Solr Document fields to attributes with single values
60
+ # Filters for fields which store the Valkyrie resource type
61
+ # @param [Hash] attribute_hash
62
+ # @return [Hash]
48
63
  def add_single_values(attribute_hash)
49
64
  attribute_hash.select do |k, v|
50
65
  field = k.to_s.split("_").last
@@ -56,15 +71,25 @@ module Valkyrie::Persistence::Solr
56
71
  end
57
72
  end
58
73
 
74
+ # Determines whether or not a field is multivalued
75
+ # @note this is tied to conventions in the Solr Schema
76
+ # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
77
+ # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
78
+ # @param [String] field
79
+ # @return [Boolean]
59
80
  def multivalued?(field)
60
81
  field.end_with?('m', 'mv')
61
82
  end
62
83
 
84
+ # If optimistic locking is enabled for this Valkyrie Resource, generates a Hash containing the locking token
85
+ # @return [Hash]
63
86
  def lock_hash
64
87
  return {} unless resource.optimistic_locking_enabled? && lock_token.present?
65
88
  { _version_: lock_token }
66
89
  end
67
90
 
91
+ # Retrieves the lock token from the resource attributes
92
+ # @return [String]
68
93
  def lock_token
69
94
  @lock_token ||= begin
70
95
  found_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
@@ -74,6 +99,8 @@ module Valkyrie::Persistence::Solr
74
99
  end
75
100
  end
76
101
 
102
+ # Generates the Valkyrie Resource attribute Hash
103
+ # @return [Hash]
77
104
  def attribute_hash
78
105
  properties.each_with_object({}) do |property, hsh|
79
106
  next if property == Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK
@@ -87,10 +114,14 @@ module Valkyrie::Persistence::Solr
87
114
  end
88
115
  end
89
116
 
117
+ # Accesses the keys for the attributes on the Valkyrie Resource
118
+ # @return [Array<Symbol>]
90
119
  def properties
91
120
  resource_attributes.keys - [:id, :created_at, :updated_at, :new_record]
92
121
  end
93
122
 
123
+ # Access the attributes for the Valkyrie resources
124
+ # @return [Hash]
94
125
  def resource_attributes
95
126
  @resource_attributes ||= resource.attributes
96
127
  end
@@ -143,10 +174,14 @@ module Valkyrie::Persistence::Solr
143
174
  # just one.
144
175
  class CompositeSolrRow
145
176
  attr_reader :solr_rows
177
+
178
+ # @param [Array<Valkyrie::Persistence::Solr::Mapper::SolrRow>] solr_rows
146
179
  def initialize(solr_rows)
147
180
  @solr_rows = solr_rows
148
181
  end
149
182
 
183
+ # Merge a Hash of attribute values into a logical row of Solr fields
184
+ # @param [Hash] hsh
150
185
  # @see Valkyrie::Persistence::Solr::Mapper::SolrRow#apply_to
151
186
  def apply_to(hsh)
152
187
  solr_rows.each do |solr_row|
@@ -163,10 +198,17 @@ module Valkyrie::Persistence::Solr
163
198
  # Casts {Boolean} values into a recognizable string in Solr.
164
199
  class BooleanPropertyValue < ::Valkyrie::ValueMapper
165
200
  SolrMapperValue.register(self)
201
+
202
+ # Determines whether or not a Property value behaves like a boolean value
203
+ # @param [Object] value
204
+ # @return [Boolean]
166
205
  def self.handles?(value)
167
206
  value.is_a?(Property) && ([true, false].include? value.value)
168
207
  end
169
208
 
209
+ # Constructs a SolrRow object for a Property with a Boolean value
210
+ # @note this prepends the string "boolean-" to the value indexed in Solr
211
+ # @return [SolrRow]
170
212
  def result
171
213
  calling_mapper.for(Property.new(value.key, "boolean-#{value.value}")).result
172
214
  end
@@ -175,10 +217,20 @@ module Valkyrie::Persistence::Solr
175
217
  # Casts nested resources into a JSON string in solr.
176
218
  class NestedObjectValue < ::Valkyrie::ValueMapper
177
219
  SolrMapperValue.register(self)
220
+
221
+ # Determines whether or not a Property value is a Hash
222
+ # @param [Object] value
223
+ # @return [Boolean]
178
224
  def self.handles?(value)
179
225
  value.value.is_a?(Hash)
180
226
  end
181
227
 
228
+ # Constructs a SolrRow object for a Property with a Hash value
229
+ # @note this prepends the string "serialized-" to the value indexed in Solr
230
+ # This is indexed as a stored multivalued text
231
+ # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
232
+ # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
233
+ # @return [SolrRow]
182
234
  def result
183
235
  SolrRow.new(key: value.key, fields: ["tsim"], values: "serialized-#{value.value.to_json}")
184
236
  end
@@ -187,10 +239,16 @@ module Valkyrie::Persistence::Solr
187
239
  # Casts enumerable values one by one.
188
240
  class EnumerableValue < ::Valkyrie::ValueMapper
189
241
  SolrMapperValue.register(self)
242
+
243
+ # Determines whether or not a Property value is an Array
244
+ # @param [Object] value
245
+ # @return [Boolean]
190
246
  def self.handles?(value)
191
247
  value.is_a?(Property) && value.value.is_a?(Array)
192
248
  end
193
249
 
250
+ # Constructs a CompositeSolrRow object for a set of Property values
251
+ # @return [CompositeSolrRow]
194
252
  def result
195
253
  CompositeSolrRow.new(
196
254
  value.value.map do |val|
@@ -203,10 +261,16 @@ module Valkyrie::Persistence::Solr
203
261
  # Skips nil values.
204
262
  class NilPropertyValue < ::Valkyrie::ValueMapper
205
263
  SolrMapperValue.register(self)
264
+
265
+ # Determines whether or not a Property value responds as nil
266
+ # @param [Object] value
267
+ # @return [Boolean]
206
268
  def self.handles?(value)
207
269
  value.is_a?(Property) && value.value.nil?
208
270
  end
209
271
 
272
+ # Constructs a SolrRow object for a Property with a nil value
273
+ # @return [SolrRow]
210
274
  def result
211
275
  SolrRow.new(key: value.key, fields: [], values: nil)
212
276
  end
@@ -215,10 +279,17 @@ module Valkyrie::Persistence::Solr
215
279
  # Casts {Valkyrie::ID} values into a recognizable string in solr.
216
280
  class IDPropertyValue < ::Valkyrie::ValueMapper
217
281
  SolrMapperValue.register(self)
282
+
283
+ # Determines whether or not a Property value is a Valkyrie ID
284
+ # @param [Object] value
285
+ # @return [Boolean]
218
286
  def self.handles?(value)
219
287
  value.is_a?(Property) && value.value.is_a?(::Valkyrie::ID)
220
288
  end
221
289
 
290
+ # Constructs a SolrRow object for the Property Valkyrie ID value
291
+ # @note this prepends the string "id-" to the value indexed in Solr
292
+ # @return [SolrRow]
222
293
  def result
223
294
  calling_mapper.for(Property.new(value.key, "id-#{value.value.id}")).result
224
295
  end
@@ -227,10 +298,17 @@ module Valkyrie::Persistence::Solr
227
298
  # Casts {RDF::URI} values into a recognizable string in solr.
228
299
  class URIPropertyValue < ::Valkyrie::ValueMapper
229
300
  SolrMapperValue.register(self)
301
+
302
+ # Determines whether or not a Property value is a URI
303
+ # @param [Object] value
304
+ # @return [Boolean]
230
305
  def self.handles?(value)
231
306
  value.is_a?(Property) && value.value.is_a?(::RDF::URI)
232
307
  end
233
308
 
309
+ # Constructs a SolrRow object for the Property URI value
310
+ # @note this prepends the string "uri-" to the value indexed in Solr
311
+ # @return [SolrRow]
234
312
  def result
235
313
  calling_mapper.for(Property.new(value.key, "uri-#{value.value}")).result
236
314
  end
@@ -239,28 +317,57 @@ module Valkyrie::Persistence::Solr
239
317
  # Casts {Integer} values into a recognizable string in Solr.
240
318
  class IntegerPropertyValue < ::Valkyrie::ValueMapper
241
319
  SolrMapperValue.register(self)
320
+
321
+ # Determines whether or not a Property value is an Integer
322
+ # @param [Object] value
323
+ # @return [Boolean]
242
324
  def self.handles?(value)
243
325
  value.is_a?(Property) && value.value.is_a?(Integer)
244
326
  end
245
327
 
328
+ # Constructs a SolrRow object for the Property Integer value
329
+ # @note this prepends the string "integer-" to the value indexed in Solr
330
+ # @return [SolrRow]
246
331
  def result
247
332
  calling_mapper.for(Property.new(value.key, "integer-#{value.value}")).result
248
333
  end
249
334
  end
250
335
 
336
+ # Casts {Float} values into a recognizable string in Solr.
337
+ class FloatPropertyValue < ::Valkyrie::ValueMapper
338
+ SolrMapperValue.register(self)
339
+ def self.handles?(value)
340
+ value.is_a?(Property) && value.value.is_a?(Float)
341
+ end
342
+
343
+ def result
344
+ calling_mapper.for(Property.new(value.key, "float-#{value.value}")).result
345
+ end
346
+ end
347
+
251
348
  # Casts {DateTime} values into a recognizable string in Solr.
252
349
  class DateTimePropertyValue < ::Valkyrie::ValueMapper
253
350
  SolrMapperValue.register(self)
351
+
352
+ # Determines whether or not a Property value is a DateTime or Time
353
+ # @param [Object] value
354
+ # @return [Boolean]
254
355
  def self.handles?(value)
255
356
  value.is_a?(Property) && (value.value.is_a?(Time) || value.value.is_a?(DateTime))
256
357
  end
257
358
 
359
+ # Constructs a SolrRow object for a datestamp derived from the value
360
+ # @note this prepends the string "datetime-" to the value indexed in Solr
361
+ # @return [SolrRow]
258
362
  def result
259
363
  calling_mapper.for(Property.new(value.key, "datetime-#{JSON.parse(to_datetime(value.value).to_json)}")).result
260
364
  end
261
365
 
262
366
  private
263
367
 
368
+ # Converts a value to a UTC timestamp if it is a DateTime or behaves like a Time value
369
+ # @param [Object] value
370
+ # @return [Time]
264
371
  def to_datetime(value)
265
372
  return value.utc if value.is_a?(DateTime)
266
373
  return value.to_datetime.utc if value.respond_to?(:to_datetime)
@@ -272,10 +379,16 @@ module Valkyrie::Persistence::Solr
272
379
  # for non-language-tagged strings.
273
380
  class SharedStringPropertyValue < ::Valkyrie::ValueMapper
274
381
  SolrMapperValue.register(self)
382
+
383
+ # Determines whether or not a Property value is a String whether or not the Property has an RDF literal specifying the language tag
384
+ # @param [Object] value
385
+ # @return [Boolean]
275
386
  def self.handles?(value)
276
387
  value.is_a?(Property) && value.value.is_a?(String) && value.scope.find { |x| x.is_a?(::RDF::Literal) }.present?
277
388
  end
278
389
 
390
+ # Constructs a CompositeSolrRow object with the language-tagged literal value
391
+ # @return [CompositeSolrRow]
279
392
  def result
280
393
  CompositeSolrRow.new(
281
394
  [
@@ -290,14 +403,32 @@ module Valkyrie::Persistence::Solr
290
403
  # Handles casting strings.
291
404
  class StringPropertyValue < ::Valkyrie::ValueMapper
292
405
  SolrMapperValue.register(self)
406
+
407
+ # Determines whether or not a Property value is a String
408
+ # @param [Object] value
409
+ # @return [Boolean]
293
410
  def self.handles?(value)
294
411
  value.is_a?(Property) && value.value.is_a?(String)
295
412
  end
296
413
 
414
+ # Constructs a SolrRow object with the String values and Solr field settings
415
+ # @return [SolrRow]
297
416
  def result
298
417
  SolrRow.new(key: value.key, fields: fields, values: value.value)
299
418
  end
300
419
 
420
+ # Generates the Solr fields used during the indexing
421
+ # String are normally indexed using the following:
422
+ # - stored text
423
+ # - stored english text
424
+ # - stored single string
425
+ # - multivalued string
426
+ # - stored multivalued text
427
+ # - stored multivalued english text
428
+ # If the string is greater than 1000 characters in length, it is only indexed as a stored multivalued text
429
+ # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
430
+ # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
431
+ # @return [Array<Symbol>]
301
432
  def fields
302
433
  if value.value.length > 1000
303
434
  [:tsim]
@@ -310,10 +441,16 @@ module Valkyrie::Persistence::Solr
310
441
  # Handles casting language-typed {RDF::Literal}s
311
442
  class LiteralPropertyValue < ::Valkyrie::ValueMapper
312
443
  SolrMapperValue.register(self)
444
+
445
+ # Determines whether or not a Property value is an RDF literal
446
+ # @param [Object] value
447
+ # @return [Boolean]
313
448
  def self.handles?(value)
314
449
  value.is_a?(Property) && value.value.is_a?(::RDF::Literal)
315
450
  end
316
451
 
452
+ # Constructs a CompositeSolrRow object with the language-tagged literal value
453
+ # @return [CompositeSolrRow]
317
454
  def result
318
455
  key = value.key
319
456
  val = value.value