valkyrie 2.0.0 → 2.1.2

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +60 -56
  3. data/.lando.yml +40 -0
  4. data/.rubocop.yml +4 -1
  5. data/.tool-versions +1 -1
  6. data/Appraisals +4 -4
  7. data/CHANGELOG.md +136 -0
  8. data/README.md +21 -49
  9. data/Rakefile +21 -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 +2 -2
  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 +16 -17
  29. data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +38 -18
  30. data/lib/valkyrie/persistence/fedora/query_service.rb +54 -53
  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 +4 -2
  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_members_query.rb +1 -1
  42. data/lib/valkyrie/persistence/solr/query_service.rb +42 -53
  43. data/lib/valkyrie/persistence/solr/repository.rb +2 -1
  44. data/lib/valkyrie/rdf_patches.rb +2 -2
  45. data/lib/valkyrie/resource.rb +36 -5
  46. data/lib/valkyrie/specs/shared_specs/change_set.rb +1 -1
  47. data/lib/valkyrie/specs/shared_specs/persister.rb +17 -6
  48. data/lib/valkyrie/specs/shared_specs/queries.rb +112 -9
  49. data/lib/valkyrie/storage/fedora.rb +17 -17
  50. data/lib/valkyrie/storage_adapter.rb +16 -13
  51. data/lib/valkyrie/types.rb +3 -1
  52. data/lib/valkyrie/version.rb +1 -1
  53. data/solr/config/solrconfig.xml +0 -10
  54. data/tasks/dev.rake +14 -51
  55. data/valkyrie.gemspec +4 -4
  56. metadata +40 -37
  57. data/.docker-stack/valkyrie-development/docker-compose.yml +0 -53
  58. data/.docker-stack/valkyrie-test/docker-compose.yml +0 -53
  59. data/db/seeds.rb +0 -8
  60. data/tasks/docker.rake +0 -31
@@ -21,65 +21,65 @@ module Valkyrie::Persistence::Postgres
21
21
 
22
22
  private
23
23
 
24
- # Construct a new Valkyrie Resource using the attributes retrieved from the database
25
- # @return [Valkyrie::Resource]
26
- def resource
27
- resource_klass.new(
28
- attributes.merge(
29
- new_record: false,
30
- Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK => lock_token
31
- )
24
+ # Construct a new Valkyrie Resource using the attributes retrieved from the database
25
+ # @return [Valkyrie::Resource]
26
+ def resource
27
+ resource_klass.new(
28
+ attributes.merge(
29
+ new_record: false,
30
+ Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK => lock_token
32
31
  )
33
- end
32
+ )
33
+ end
34
34
 
35
- # Construct the optimistic lock token using the adapter and lock version for the Resource
36
- # @return [Valkyrie::Persistence::OptimisticLockToken]
37
- def lock_token
38
- return lock_token_warning unless orm_object.class.column_names.include?("lock_version")
39
- @lock_token ||=
40
- Valkyrie::Persistence::OptimisticLockToken.new(
41
- adapter_id: resource_factory.adapter_id,
42
- token: orm_object.lock_version
43
- )
44
- end
35
+ # Construct the optimistic lock token using the adapter and lock version for the Resource
36
+ # @return [Valkyrie::Persistence::OptimisticLockToken]
37
+ def lock_token
38
+ return lock_token_warning unless orm_object.class.column_names.include?("lock_version")
39
+ @lock_token ||=
40
+ Valkyrie::Persistence::OptimisticLockToken.new(
41
+ adapter_id: resource_factory.adapter_id,
42
+ token: orm_object.lock_version
43
+ )
44
+ end
45
45
 
46
- # Issue a migration warning for previous releases of Valkyrie which did not support optimistic locking
47
- def lock_token_warning
48
- return nil unless resource_klass.optimistic_locking_enabled?
49
- warn "[MIGRATION REQUIRED] You have loaded a resource from the Postgres adapter with " \
50
- "optimistic locking enabled, but the necessary migrations have not been run. \n" \
51
- "Please run `bin/rails valkyrie_engine:install:migrations && bin/rails db:migrate` " \
52
- "to enable this feature for Postgres."
53
- nil
54
- end
46
+ # Issue a migration warning for previous releases of Valkyrie which did not support optimistic locking
47
+ def lock_token_warning
48
+ return nil unless resource_klass.optimistic_locking_enabled?
49
+ warn "[MIGRATION REQUIRED] You have loaded a resource from the Postgres adapter with " \
50
+ "optimistic locking enabled, but the necessary migrations have not been run. \n" \
51
+ "Please run `bin/rails valkyrie_engine:install:migrations && bin/rails db:migrate` " \
52
+ "to enable this feature for Postgres."
53
+ nil
54
+ end
55
55
 
56
- # Retrieve the Class used to construct the Valkyrie Resource
57
- # @return [Class]
58
- def resource_klass
59
- Valkyrie.config.resource_class_resolver.call(internal_resource)
60
- end
56
+ # Retrieve the Class used to construct the Valkyrie Resource
57
+ # @return [Class]
58
+ def resource_klass
59
+ Valkyrie.config.resource_class_resolver.call(internal_resource)
60
+ end
61
61
 
62
- # Access the String for the Valkyrie Resource type within the attributes
63
- # @return [String]
64
- def internal_resource
65
- attributes[:internal_resource]
66
- end
62
+ # Access the String for the Valkyrie Resource type within the attributes
63
+ # @return [String]
64
+ def internal_resource
65
+ attributes[:internal_resource]
66
+ end
67
67
 
68
- # @return [Hash] Valkyrie-style hash of attributes.
69
- def attributes
70
- @attributes ||= orm_object.attributes.merge(rdf_metadata).symbolize_keys
71
- end
68
+ # @return [Hash] Valkyrie-style hash of attributes.
69
+ def attributes
70
+ @attributes ||= orm_object.attributes.merge(rdf_metadata).symbolize_keys
71
+ end
72
72
 
73
- # Generate a Hash derived from Valkyrie Resource metadata encoded in the RDF
74
- # @return [Hash]
75
- def rdf_metadata
76
- @rdf_metadata ||= RDFMetadata.new(orm_object.metadata).result
77
- end
73
+ # Generate a Hash derived from Valkyrie Resource metadata encoded in the RDF
74
+ # @return [Hash]
75
+ def rdf_metadata
76
+ @rdf_metadata ||= RDFMetadata.new(orm_object.metadata).result
77
+ end
78
78
 
79
- # Responsible for converting `metadata` JSON-B field in
80
- # {Valkyrie::Persistence::Postgres::ORM::Resource} into an acceptable hash
81
- # for {Valkyrie::Resource}
82
- class RDFMetadata < ::Valkyrie::Persistence::Shared::JSONValueMapper
83
- end
79
+ # Responsible for converting `metadata` JSON-B field in
80
+ # {Valkyrie::Persistence::Postgres::ORM::Resource} into an acceptable hash
81
+ # for {Valkyrie::Resource}
82
+ class RDFMetadata < ::Valkyrie::Persistence::Shared::JSONValueMapper
83
+ end
84
84
  end
85
85
  end
@@ -35,6 +35,13 @@ module Valkyrie::Persistence::Postgres
35
35
  end
36
36
  end
37
37
 
38
+ # Count all records for a specific resource type
39
+ # @param [Class] model
40
+ # @return integer
41
+ def count_all_of_model(model:)
42
+ orm_class.where(internal_resource: model.to_s).count
43
+ end
44
+
38
45
  # Find a record using a Valkyrie ID, and map it to a Valkyrie Resource
39
46
  # @param [Valkyrie::ID, String] id
40
47
  # @return [Valkyrie::Resource]
@@ -94,30 +101,28 @@ module Valkyrie::Persistence::Postgres
94
101
  find_inverse_references_by(resource: resource, property: :member_ids)
95
102
  end
96
103
 
97
- # Find all resources related to a given Valkyrie Resource by a property
98
- # @param [Valkyrie::Resource] resource
99
- # @param [String] property
100
- # @return [Array<Valkyrie::Resource>]
101
- def find_references_by(resource:, property:)
104
+ # (see Valkyrie::Persistence::Memory::QueryService#find_references_by)
105
+ def find_references_by(resource:, property:, model: nil)
102
106
  return [] if resource.id.blank? || resource[property].blank?
103
107
  # only return ordered if needed to avoid performance penalties
104
108
  if ordered_property?(resource: resource, property: property)
105
- run_query(find_ordered_references_query, property, resource.id.to_s)
109
+ find_ordered_references_by(resource: resource, property: property, model: model)
106
110
  else
107
- run_query(find_references_query, property, resource.id.to_s)
111
+ find_unordered_references_by(resource: resource, property: property, model: model)
108
112
  end
109
113
  end
110
114
 
111
- # Find all resources referencing a given Valkyrie Resource by a property
112
- # @param [Valkyrie::Resource] resource
113
- # @param [String] property
114
- # @return [Array<Valkyrie::Resource>]
115
- def find_inverse_references_by(resource: nil, id: nil, property:)
115
+ # (see Valkyrie::Persistence::Memory::QueryService#find_inverse_references_by)
116
+ def find_inverse_references_by(resource: nil, id: nil, property:, model: nil)
116
117
  raise ArgumentError, "Provide resource or id" unless resource || id
117
118
  ensure_persisted(resource) if resource
118
119
  id ||= resource.id
119
120
  internal_array = "{\"#{property}\": [{\"id\": \"#{id}\"}]}"
120
- run_query(find_inverse_references_query, internal_array)
121
+ if model
122
+ run_query(find_inverse_references_with_type_query, internal_array, model)
123
+ else
124
+ run_query(find_inverse_references_query, internal_array)
125
+ end
121
126
  end
122
127
 
123
128
  # Execute a query in SQL for resource records and map them to Valkyrie
@@ -180,6 +185,21 @@ module Valkyrie::Persistence::Postgres
180
185
  SQL
181
186
  end
182
187
 
188
+ # Generate the SQL query for retrieving member resources in PostgreSQL using a
189
+ # JSON object literal (e. g. { "alternate_ids": [{"id": "d6e88f80-41b3-4dbf-a2a0-cd79e20f6d10"}] }).
190
+ # and resource type as arguments
191
+ # @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
192
+ # This uses JSON functions in order to retrieve JSON property values
193
+ # @see https://www.postgresql.org/docs/current/static/functions-json.html
194
+ # @return [String]
195
+ def find_inverse_references_with_type_query
196
+ <<-SQL
197
+ SELECT * FROM orm_resources WHERE
198
+ metadata @> ?
199
+ AND internal_resource = ?
200
+ SQL
201
+ end
202
+
183
203
  # Generate the SQL query for retrieving member resources in PostgreSQL using a
184
204
  # JSON object literal and resource ID as arguments.
185
205
  # @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
@@ -197,6 +217,14 @@ module Valkyrie::Persistence::Postgres
197
217
  SQL
198
218
  end
199
219
 
220
+ def find_references_with_type_query
221
+ <<-SQL
222
+ SELECT DISTINCT member.* FROM orm_resources a,
223
+ jsonb_array_elements(a.metadata->?) AS b(member)
224
+ JOIN orm_resources member ON (b.member->>'id')::#{id_type} = member.id WHERE a.id = ? AND member.internal_resource = ?
225
+ SQL
226
+ end
227
+
200
228
  def find_ordered_references_query
201
229
  <<-SQL
202
230
  SELECT member.* FROM orm_resources a,
@@ -206,6 +234,15 @@ module Valkyrie::Persistence::Postgres
206
234
  SQL
207
235
  end
208
236
 
237
+ def find_ordered_references_with_type_query
238
+ <<-SQL
239
+ SELECT member.* FROM orm_resources a,
240
+ jsonb_array_elements(a.metadata->?) WITH ORDINALITY AS b(member, member_pos)
241
+ JOIN orm_resources member ON (b.member->>'id')::#{id_type} = member.id WHERE a.id = ? AND member.internal_resource = ?
242
+ ORDER BY b.member_pos
243
+ SQL
244
+ end
245
+
209
246
  # Constructs a Valkyrie::Persistence::CustomQueryContainer using this query service
210
247
  # @return [Valkyrie::Persistence::CustomQueryContainer]
211
248
  def custom_queries
@@ -214,30 +251,46 @@ module Valkyrie::Persistence::Postgres
214
251
 
215
252
  private
216
253
 
217
- # Determines whether or not an Object is a Valkyrie ID
218
- # @param [Object] id
219
- # @raise [ArgumentError]
220
- def validate_id(id)
221
- raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
254
+ def find_ordered_references_by(resource:, property:, model: nil)
255
+ if model
256
+ run_query(find_ordered_references_with_type_query, property, resource.id.to_s, model)
257
+ else
258
+ run_query(find_ordered_references_query, property, resource.id.to_s)
222
259
  end
260
+ end
223
261
 
224
- # Determines whether or not a resource has been persisted
225
- # @param [Object] resource
226
- # @raise [ArgumentError]
227
- def ensure_persisted(resource)
228
- raise ArgumentError, 'resource is not saved' unless resource.persisted?
262
+ def find_unordered_references_by(resource:, property:, model: nil)
263
+ if model
264
+ run_query(find_references_with_type_query, property, resource.id.to_s, model)
265
+ else
266
+ run_query(find_references_query, property, resource.id.to_s)
229
267
  end
268
+ end
230
269
 
231
- # Accesses the data type in PostgreSQL used for the primary key
232
- # (For example, a UUID)
233
- # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaCache.html#method-i-columns_hash
234
- # @return [Symbol]
235
- def id_type
236
- @id_type ||= orm_class.columns_hash["id"].type
237
- end
270
+ # Determines whether or not an Object is a Valkyrie ID
271
+ # @param [Object] id
272
+ # @raise [ArgumentError]
273
+ def validate_id(id)
274
+ raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
275
+ end
238
276
 
239
- def ordered_property?(resource:, property:)
240
- resource.ordered_attribute?(property)
241
- end
277
+ # Determines whether or not a resource has been persisted
278
+ # @param [Object] resource
279
+ # @raise [ArgumentError]
280
+ def ensure_persisted(resource)
281
+ raise ArgumentError, 'resource is not saved' unless resource.persisted?
282
+ end
283
+
284
+ # Accesses the data type in PostgreSQL used for the primary key
285
+ # (For example, a UUID)
286
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaCache.html#method-i-columns_hash
287
+ # @return [Symbol]
288
+ def id_type
289
+ @id_type ||= orm_class.columns_hash["id"].type
290
+ end
291
+
292
+ def ordered_property?(resource:, property:)
293
+ resource.ordered_attribute?(property)
294
+ end
242
295
  end
243
296
  end
@@ -20,7 +20,7 @@ module Valkyrie::Persistence::Postgres
20
20
  orm_object.internal_resource = resource.internal_resource
21
21
  process_lock_token(orm_object)
22
22
  orm_object.disable_optimistic_locking! unless resource.optimistic_locking_enabled?
23
- orm_object.metadata.merge!(attributes)
23
+ orm_object.metadata = attributes
24
24
  end
25
25
  end
26
26
 
@@ -137,20 +137,22 @@ module Valkyrie::Persistence::Shared
137
137
  # e. g. 1970-01-01
138
138
  # @param [Object] value
139
139
  # @return [Boolean]
140
+ # rubocop:disable Metrics/CyclomaticComplexity
140
141
  def self.handles?(value)
141
142
  return false unless value.is_a?(String)
142
- return false unless value[4] == "-"
143
+ return false unless value[4] == "-" && value[10] == "T"
143
144
  year = value.to_s[0..3]
144
145
  return false unless year.length == 4 && year.to_i.to_s == year
145
146
  DateTime.iso8601(value)
146
147
  rescue
147
148
  false
148
149
  end
150
+ # rubocop:enable Metrics/CyclomaticComplexity
149
151
 
150
152
  # Generates a Time object in the UTC from the datestamp string value
151
153
  # @return [Time]
152
154
  def result
153
- DateTime.iso8601(value).utc
155
+ DateTime.iso8601(value).new_offset(0)
154
156
  end
155
157
  end
156
158
  end
@@ -65,412 +65,412 @@ module Valkyrie::Persistence::Solr
65
65
 
66
66
  private
67
67
 
68
- # Maps Solr Document fields to attributes with single values
69
- # Filters for fields which store the Valkyrie resource type
70
- # @param [Hash] attribute_hash
71
- # @return [Hash]
72
- def add_single_values(attribute_hash)
73
- attribute_hash.select do |k, v|
74
- field = k.to_s.split("_").last
75
- property = k.to_s.gsub("_#{field}", "")
76
- next true if multivalued?(field)
77
- next false if property == "internal_resource"
78
- next false if v.length > 1
79
- true
80
- end
68
+ # Maps Solr Document fields to attributes with single values
69
+ # Filters for fields which store the Valkyrie resource type
70
+ # @param [Hash] attribute_hash
71
+ # @return [Hash]
72
+ def add_single_values(attribute_hash)
73
+ attribute_hash.select do |k, v|
74
+ field = k.to_s.split("_").last
75
+ property = k.to_s.gsub("_#{field}", "")
76
+ next true if multivalued?(field)
77
+ next false if property == "internal_resource"
78
+ next false if v.length > 1
79
+ true
81
80
  end
81
+ end
82
82
 
83
- # Determines whether or not a field is multivalued
84
- # @note this is tied to conventions in the Solr Schema
85
- # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
86
- # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
87
- # @param [String] field
88
- # @return [Boolean]
89
- def multivalued?(field)
90
- field.end_with?('m', 'mv')
91
- end
83
+ # Determines whether or not a field is multivalued
84
+ # @note this is tied to conventions in the Solr Schema
85
+ # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
86
+ # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
87
+ # @param [String] field
88
+ # @return [Boolean]
89
+ def multivalued?(field)
90
+ field.end_with?('m', 'mv')
91
+ end
92
92
 
93
- # If optimistic locking is enabled for this Valkyrie Resource, generates a Hash containing the locking token
94
- # @return [Hash]
95
- def lock_hash
96
- return {} unless resource.optimistic_locking_enabled? && lock_token.present?
97
- { _version_: lock_token }
98
- end
93
+ # If optimistic locking is enabled for this Valkyrie Resource, generates a Hash containing the locking token
94
+ # @return [Hash]
95
+ def lock_hash
96
+ return {} unless resource.optimistic_locking_enabled? && lock_token.present?
97
+ { _version_: lock_token }
98
+ end
99
99
 
100
- # Retrieves the lock token from the resource attributes
101
- # @return [String]
102
- def lock_token
103
- @lock_token ||= begin
104
- found_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
105
- .find { |token| token.adapter_id == resource_factory.adapter_id }
106
- return if found_token.nil?
107
- found_token.token
108
- end
100
+ # Retrieves the lock token from the resource attributes
101
+ # @return [String]
102
+ def lock_token
103
+ @lock_token ||= begin
104
+ found_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
105
+ .find { |token| token.adapter_id == resource_factory.adapter_id }
106
+ return if found_token.nil?
107
+ found_token.token
109
108
  end
109
+ end
110
110
 
111
- # Generates the Valkyrie Resource attribute Hash
112
- # @return [Hash]
113
- def attribute_hash
114
- properties.each_with_object({}) do |property, hsh|
115
- next if property == Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK
116
- attr = resource_attributes[property]
117
- mapper_val = SolrMapperValue.for(Property.new(property, attr)).result
118
- unless mapper_val.respond_to?(:apply_to)
119
- raise "Unable to cast #{resource_attributes[:internal_resource]}#" \
120
- "#{property} which has been set to an instance of '#{attr.class}'"
121
- end
122
- mapper_val.apply_to(hsh)
123
- end
111
+ # Generates the Valkyrie Resource attribute Hash
112
+ # @return [Hash]
113
+ def attribute_hash
114
+ properties.each_with_object({}) do |property, hsh|
115
+ next if property == Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK
116
+ attr = resource_attributes[property]
117
+ mapper_val = SolrMapperValue.for(Property.new(property, attr)).result
118
+ unless mapper_val.respond_to?(:apply_to)
119
+ raise "Unable to cast #{resource_attributes[:internal_resource]}#" \
120
+ "#{property} which has been set to an instance of '#{attr.class}'"
121
+ end
122
+ mapper_val.apply_to(hsh)
124
123
  end
124
+ end
125
125
 
126
- # Accesses the keys for the attributes on the Valkyrie Resource
127
- # @return [Array<Symbol>]
128
- def properties
129
- resource_attributes.keys - [:id, :created_at, :updated_at, :new_record]
130
- end
131
-
132
- # Access the attributes for the Valkyrie resources
133
- # @return [Hash]
134
- def resource_attributes
135
- @resource_attributes ||= resource.attributes
136
- end
137
-
138
- ##
139
- # A container resource for holding a `key`, `value, and `scope` of a value
140
- # in a resource together for casting.
141
- class Property
142
- attr_reader :key, :value, :scope
143
- # @param key [Symbol] Property identifier.
144
- # @param value [Object] Value or list of values which are underneath the
145
- # key.
146
- # @param scope [Object] The resource or point where the key and values
147
- # came from.
148
- def initialize(key, value, scope = [])
149
- @key = key
150
- @value = value
151
- @scope = scope
152
- end
126
+ # Accesses the keys for the attributes on the Valkyrie Resource
127
+ # @return [Array<Symbol>]
128
+ def properties
129
+ resource_attributes.keys - [:id, :created_at, :updated_at, :new_record]
130
+ end
131
+
132
+ # Access the attributes for the Valkyrie resources
133
+ # @return [Hash]
134
+ def resource_attributes
135
+ @resource_attributes ||= resource.attributes
136
+ end
137
+
138
+ ##
139
+ # A container resource for holding a `key`, `value, and `scope` of a value
140
+ # in a resource together for casting.
141
+ class Property
142
+ attr_reader :key, :value, :scope
143
+ # @param key [Symbol] Property identifier.
144
+ # @param value [Object] Value or list of values which are underneath the
145
+ # key.
146
+ # @param scope [Object] The resource or point where the key and values
147
+ # came from.
148
+ def initialize(key, value, scope = [])
149
+ @key = key
150
+ @value = value
151
+ @scope = scope
153
152
  end
153
+ end
154
154
 
155
- ##
156
- # Represents a key/value combination in the solr document, used for isolating logic around
157
- # how to apply a value to a hash.
158
- class SolrRow
159
- attr_reader :key, :fields, :values
160
- # @param key [Symbol] Solr key.
161
- # @param fields [Array<Symbol>] Field suffixes to index into.
162
- # @param values [Array] Values to index into the given fields.
163
- def initialize(key:, fields:, values:)
164
- @key = key
165
- @fields = Array.wrap(fields)
166
- @values = Array.wrap(values)
167
- end
155
+ ##
156
+ # Represents a key/value combination in the solr document, used for isolating logic around
157
+ # how to apply a value to a hash.
158
+ class SolrRow
159
+ attr_reader :key, :fields, :values
160
+ # @param key [Symbol] Solr key.
161
+ # @param fields [Array<Symbol>] Field suffixes to index into.
162
+ # @param values [Array] Values to index into the given fields.
163
+ def initialize(key:, fields:, values:)
164
+ @key = key
165
+ @fields = Array.wrap(fields)
166
+ @values = Array.wrap(values)
167
+ end
168
168
 
169
- # @param hsh [Hash] The solr hash to apply to.
170
- # @return [Hash] The updated solr hash.
171
- def apply_to(hsh)
172
- return hsh if values.blank?
173
- fields.each do |field|
174
- hsh["#{key}_#{field}".to_sym] ||= []
175
- hsh["#{key}_#{field}".to_sym] += values
176
- end
177
- hsh
169
+ # @param hsh [Hash] The solr hash to apply to.
170
+ # @return [Hash] The updated solr hash.
171
+ def apply_to(hsh)
172
+ return hsh if values.blank?
173
+ fields.each do |field|
174
+ hsh["#{key}_#{field}".to_sym] ||= []
175
+ hsh["#{key}_#{field}".to_sym] += values
178
176
  end
177
+ hsh
179
178
  end
179
+ end
180
180
 
181
- ##
182
- # Wraps up multiple SolrRows to apply them all at once, while looking like
183
- # just one.
184
- class CompositeSolrRow
185
- attr_reader :solr_rows
181
+ ##
182
+ # Wraps up multiple SolrRows to apply them all at once, while looking like
183
+ # just one.
184
+ class CompositeSolrRow
185
+ attr_reader :solr_rows
186
186
 
187
- # @param [Array<Valkyrie::Persistence::Solr::Mapper::SolrRow>] solr_rows
188
- def initialize(solr_rows)
189
- @solr_rows = solr_rows
190
- end
187
+ # @param [Array<Valkyrie::Persistence::Solr::Mapper::SolrRow>] solr_rows
188
+ def initialize(solr_rows)
189
+ @solr_rows = solr_rows
190
+ end
191
191
 
192
- # Merge a Hash of attribute values into a logical row of Solr fields
193
- # @param [Hash] hsh
194
- # @see Valkyrie::Persistence::Solr::Mapper::SolrRow#apply_to
195
- def apply_to(hsh)
196
- solr_rows.each do |solr_row|
197
- solr_row.apply_to(hsh)
198
- end
199
- hsh
192
+ # Merge a Hash of attribute values into a logical row of Solr fields
193
+ # @param [Hash] hsh
194
+ # @see Valkyrie::Persistence::Solr::Mapper::SolrRow#apply_to
195
+ def apply_to(hsh)
196
+ solr_rows.each do |solr_row|
197
+ solr_row.apply_to(hsh)
200
198
  end
199
+ hsh
201
200
  end
201
+ end
202
202
 
203
- # Container for casting mappers.
204
- class SolrMapperValue < ::Valkyrie::ValueMapper
205
- end
203
+ # Container for casting mappers.
204
+ class SolrMapperValue < ::Valkyrie::ValueMapper
205
+ end
206
206
 
207
- # Casts {Boolean} values into a recognizable string in Solr.
208
- class BooleanPropertyValue < ::Valkyrie::ValueMapper
209
- SolrMapperValue.register(self)
207
+ # Casts {Boolean} values into a recognizable string in Solr.
208
+ class BooleanPropertyValue < ::Valkyrie::ValueMapper
209
+ SolrMapperValue.register(self)
210
210
 
211
- # Determines whether or not a Property value behaves like a boolean value
212
- # @param [Object] value
213
- # @return [Boolean]
214
- def self.handles?(value)
215
- value.is_a?(Property) && ([true, false].include? value.value)
216
- end
211
+ # Determines whether or not a Property value behaves like a boolean value
212
+ # @param [Object] value
213
+ # @return [Boolean]
214
+ def self.handles?(value)
215
+ value.is_a?(Property) && ([true, false].include? value.value)
216
+ end
217
217
 
218
- # Constructs a SolrRow object for a Property with a Boolean value
219
- # @note this prepends the string "boolean-" to the value indexed in Solr
220
- # @return [SolrRow]
221
- def result
222
- calling_mapper.for(Property.new(value.key, "boolean-#{value.value}")).result
223
- end
218
+ # Constructs a SolrRow object for a Property with a Boolean value
219
+ # @note this prepends the string "boolean-" to the value indexed in Solr
220
+ # @return [SolrRow]
221
+ def result
222
+ calling_mapper.for(Property.new(value.key, "boolean-#{value.value}")).result
224
223
  end
224
+ end
225
225
 
226
- # Casts nested resources into a JSON string in solr.
227
- class NestedObjectValue < ::Valkyrie::ValueMapper
228
- SolrMapperValue.register(self)
226
+ # Casts nested resources into a JSON string in solr.
227
+ class NestedObjectValue < ::Valkyrie::ValueMapper
228
+ SolrMapperValue.register(self)
229
229
 
230
- # Determines whether or not a Property value is a Hash
231
- # @param [Object] value
232
- # @return [Boolean]
233
- def self.handles?(value)
234
- value.value.is_a?(Hash) || value.value.is_a?(Valkyrie::Resource)
235
- end
230
+ # Determines whether or not a Property value is a Hash
231
+ # @param [Object] value
232
+ # @return [Boolean]
233
+ def self.handles?(value)
234
+ value.value.is_a?(Hash) || value.value.is_a?(Valkyrie::Resource)
235
+ end
236
236
 
237
- # Constructs a SolrRow object for a Property with a Hash value
238
- # @note this prepends the string "serialized-" to the value indexed in Solr
239
- # This is indexed as a stored multivalued text
240
- # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
241
- # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
242
- # @return [SolrRow]
243
- def result
244
- SolrRow.new(key: value.key, fields: ["tsim"], values: "serialized-#{value.value.to_json}")
245
- end
237
+ # Constructs a SolrRow object for a Property with a Hash value
238
+ # @note this prepends the string "serialized-" to the value indexed in Solr
239
+ # This is indexed as a stored multivalued text
240
+ # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
241
+ # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
242
+ # @return [SolrRow]
243
+ def result
244
+ SolrRow.new(key: value.key, fields: ["tsim"], values: "serialized-#{value.value.to_json}")
246
245
  end
246
+ end
247
247
 
248
- # Casts enumerable values one by one.
249
- class EnumerableValue < ::Valkyrie::ValueMapper
250
- SolrMapperValue.register(self)
248
+ # Casts enumerable values one by one.
249
+ class EnumerableValue < ::Valkyrie::ValueMapper
250
+ SolrMapperValue.register(self)
251
251
 
252
- # Determines whether or not a Property value is an Array
253
- # @param [Object] value
254
- # @return [Boolean]
255
- def self.handles?(value)
256
- value.is_a?(Property) && value.value.is_a?(Array)
257
- end
252
+ # Determines whether or not a Property value is an Array
253
+ # @param [Object] value
254
+ # @return [Boolean]
255
+ def self.handles?(value)
256
+ value.is_a?(Property) && value.value.is_a?(Array)
257
+ end
258
258
 
259
- # Constructs a CompositeSolrRow object for a set of Property values
260
- # @return [CompositeSolrRow]
261
- def result
262
- CompositeSolrRow.new(
263
- value.value.map do |val|
264
- calling_mapper.for(Property.new(value.key, val, value.value)).result
265
- end
266
- )
267
- end
259
+ # Constructs a CompositeSolrRow object for a set of Property values
260
+ # @return [CompositeSolrRow]
261
+ def result
262
+ CompositeSolrRow.new(
263
+ value.value.map do |val|
264
+ calling_mapper.for(Property.new(value.key, val, value.value)).result
265
+ end
266
+ )
268
267
  end
268
+ end
269
269
 
270
- # Skips nil values.
271
- class NilPropertyValue < ::Valkyrie::ValueMapper
272
- SolrMapperValue.register(self)
270
+ # Skips nil values.
271
+ class NilPropertyValue < ::Valkyrie::ValueMapper
272
+ SolrMapperValue.register(self)
273
273
 
274
- # Determines whether or not a Property value responds as nil
275
- # @param [Object] value
276
- # @return [Boolean]
277
- def self.handles?(value)
278
- value.is_a?(Property) && value.value.nil?
279
- end
274
+ # Determines whether or not a Property value responds as nil
275
+ # @param [Object] value
276
+ # @return [Boolean]
277
+ def self.handles?(value)
278
+ value.is_a?(Property) && value.value.nil?
279
+ end
280
280
 
281
- # Constructs a SolrRow object for a Property with a nil value
282
- # @return [SolrRow]
283
- def result
284
- SolrRow.new(key: value.key, fields: [], values: nil)
285
- end
281
+ # Constructs a SolrRow object for a Property with a nil value
282
+ # @return [SolrRow]
283
+ def result
284
+ SolrRow.new(key: value.key, fields: [], values: nil)
286
285
  end
286
+ end
287
287
 
288
- # Casts {Valkyrie::ID} values into a recognizable string in solr.
289
- class IDPropertyValue < ::Valkyrie::ValueMapper
290
- SolrMapperValue.register(self)
288
+ # Casts {Valkyrie::ID} values into a recognizable string in solr.
289
+ class IDPropertyValue < ::Valkyrie::ValueMapper
290
+ SolrMapperValue.register(self)
291
291
 
292
- # Determines whether or not a Property value is a Valkyrie ID
293
- # @param [Object] value
294
- # @return [Boolean]
295
- def self.handles?(value)
296
- value.is_a?(Property) && value.value.is_a?(::Valkyrie::ID)
297
- end
292
+ # Determines whether or not a Property value is a Valkyrie ID
293
+ # @param [Object] value
294
+ # @return [Boolean]
295
+ def self.handles?(value)
296
+ value.is_a?(Property) && value.value.is_a?(::Valkyrie::ID)
297
+ end
298
298
 
299
- # Constructs a SolrRow object for the Property Valkyrie ID value
300
- # @note this prepends the string "id-" to the value indexed in Solr
301
- # @return [SolrRow]
302
- def result
303
- calling_mapper.for(Property.new(value.key, "id-#{value.value.id}")).result
304
- end
299
+ # Constructs a SolrRow object for the Property Valkyrie ID value
300
+ # @note this prepends the string "id-" to the value indexed in Solr
301
+ # @return [SolrRow]
302
+ def result
303
+ calling_mapper.for(Property.new(value.key, "id-#{value.value.id}")).result
305
304
  end
305
+ end
306
306
 
307
- # Casts {RDF::URI} values into a recognizable string in solr.
308
- class URIPropertyValue < ::Valkyrie::ValueMapper
309
- SolrMapperValue.register(self)
307
+ # Casts {RDF::URI} values into a recognizable string in solr.
308
+ class URIPropertyValue < ::Valkyrie::ValueMapper
309
+ SolrMapperValue.register(self)
310
310
 
311
- # Determines whether or not a Property value is a URI
312
- # @param [Object] value
313
- # @return [Boolean]
314
- def self.handles?(value)
315
- value.is_a?(Property) && value.value.is_a?(::RDF::URI)
316
- end
311
+ # Determines whether or not a Property value is a URI
312
+ # @param [Object] value
313
+ # @return [Boolean]
314
+ def self.handles?(value)
315
+ value.is_a?(Property) && value.value.is_a?(::RDF::URI)
316
+ end
317
317
 
318
- # Constructs a SolrRow object for the Property URI value
319
- # @note this prepends the string "uri-" to the value indexed in Solr
320
- # @return [SolrRow]
321
- def result
322
- calling_mapper.for(Property.new(value.key, "uri-#{value.value}")).result
323
- end
318
+ # Constructs a SolrRow object for the Property URI value
319
+ # @note this prepends the string "uri-" to the value indexed in Solr
320
+ # @return [SolrRow]
321
+ def result
322
+ calling_mapper.for(Property.new(value.key, "uri-#{value.value}")).result
324
323
  end
324
+ end
325
325
 
326
- # Casts {Integer} values into a recognizable string in Solr.
327
- class IntegerPropertyValue < ::Valkyrie::ValueMapper
328
- SolrMapperValue.register(self)
326
+ # Casts {Integer} values into a recognizable string in Solr.
327
+ class IntegerPropertyValue < ::Valkyrie::ValueMapper
328
+ SolrMapperValue.register(self)
329
329
 
330
- # Determines whether or not a Property value is an Integer
331
- # @param [Object] value
332
- # @return [Boolean]
333
- def self.handles?(value)
334
- value.is_a?(Property) && value.value.is_a?(Integer)
335
- end
330
+ # Determines whether or not a Property value is an Integer
331
+ # @param [Object] value
332
+ # @return [Boolean]
333
+ def self.handles?(value)
334
+ value.is_a?(Property) && value.value.is_a?(Integer)
335
+ end
336
336
 
337
- # Constructs a SolrRow object for the Property Integer value
338
- # @note this prepends the string "integer-" to the value indexed in Solr
339
- # @return [SolrRow]
340
- def result
341
- calling_mapper.for(Property.new(value.key, "integer-#{value.value}")).result
342
- end
337
+ # Constructs a SolrRow object for the Property Integer value
338
+ # @note this prepends the string "integer-" to the value indexed in Solr
339
+ # @return [SolrRow]
340
+ def result
341
+ calling_mapper.for(Property.new(value.key, "integer-#{value.value}")).result
343
342
  end
343
+ end
344
344
 
345
- # Casts {Float} values into a recognizable string in Solr.
346
- class FloatPropertyValue < ::Valkyrie::ValueMapper
347
- SolrMapperValue.register(self)
348
- def self.handles?(value)
349
- value.is_a?(Property) && value.value.is_a?(Float)
350
- end
345
+ # Casts {Float} values into a recognizable string in Solr.
346
+ class FloatPropertyValue < ::Valkyrie::ValueMapper
347
+ SolrMapperValue.register(self)
348
+ def self.handles?(value)
349
+ value.is_a?(Property) && value.value.is_a?(Float)
350
+ end
351
351
 
352
- def result
353
- calling_mapper.for(Property.new(value.key, "float-#{value.value}")).result
354
- end
352
+ def result
353
+ calling_mapper.for(Property.new(value.key, "float-#{value.value}")).result
355
354
  end
355
+ end
356
356
 
357
- # Casts {DateTime} values into a recognizable string in Solr.
358
- class DateTimePropertyValue < ::Valkyrie::ValueMapper
359
- SolrMapperValue.register(self)
357
+ # Casts {DateTime} values into a recognizable string in Solr.
358
+ class DateTimePropertyValue < ::Valkyrie::ValueMapper
359
+ SolrMapperValue.register(self)
360
360
 
361
- # Determines whether or not a Property value is a DateTime or Time
362
- # @param [Object] value
363
- # @return [Boolean]
364
- def self.handles?(value)
365
- value.is_a?(Property) && (value.value.is_a?(Time) || value.value.is_a?(DateTime))
366
- end
361
+ # Determines whether or not a Property value is a DateTime or Time
362
+ # @param [Object] value
363
+ # @return [Boolean]
364
+ def self.handles?(value)
365
+ value.is_a?(Property) && (value.value.is_a?(Time) || value.value.is_a?(DateTime))
366
+ end
367
367
 
368
- # Constructs a SolrRow object for a datestamp derived from the value
369
- # @note this prepends the string "datetime-" to the value indexed in Solr
370
- # @return [SolrRow]
371
- def result
372
- calling_mapper.for(Property.new(value.key, "datetime-#{JSON.parse(to_datetime(value.value).to_json)}")).result
373
- end
368
+ # Constructs a SolrRow object for a datestamp derived from the value
369
+ # @note this prepends the string "datetime-" to the value indexed in Solr
370
+ # @return [SolrRow]
371
+ def result
372
+ calling_mapper.for(Property.new(value.key, "datetime-#{JSON.parse(to_datetime(value.value).to_json)}")).result
373
+ end
374
374
 
375
- private
375
+ private
376
376
 
377
- # Converts a value to a UTC timestamp if it is a DateTime or behaves like a Time value
378
- # @param [Object] value
379
- # @return [Time]
380
- def to_datetime(value)
381
- return value.utc if value.is_a?(DateTime)
382
- return value.to_datetime.utc if value.respond_to?(:to_datetime)
383
- end
377
+ # Converts a value to a UTC timestamp if it is a DateTime or behaves like a Time value
378
+ # @param [Object] value
379
+ # @return [Time]
380
+ def to_datetime(value)
381
+ return value.new_offset(0) if value.is_a?(DateTime)
382
+ return value.to_datetime.new_offset(0) if value.respond_to?(:to_datetime)
384
383
  end
384
+ end
385
385
 
386
- # Handles casting language-tagged strings when there are both
387
- # language-tagged and non-language-tagged strings in Solr. Assumes English
388
- # for non-language-tagged strings.
389
- class SharedStringPropertyValue < ::Valkyrie::ValueMapper
390
- SolrMapperValue.register(self)
386
+ # Handles casting language-tagged strings when there are both
387
+ # language-tagged and non-language-tagged strings in Solr. Assumes English
388
+ # for non-language-tagged strings.
389
+ class SharedStringPropertyValue < ::Valkyrie::ValueMapper
390
+ SolrMapperValue.register(self)
391
391
 
392
- # Determines whether or not a Property value is a String whether or not the Property has an RDF literal specifying the language tag
393
- # @param [Object] value
394
- # @return [Boolean]
395
- def self.handles?(value)
396
- value.is_a?(Property) && value.value.is_a?(String) && value.scope.find { |x| x.is_a?(::RDF::Literal) }.present?
397
- end
392
+ # Determines whether or not a Property value is a String whether or not the Property has an RDF literal specifying the language tag
393
+ # @param [Object] value
394
+ # @return [Boolean]
395
+ def self.handles?(value)
396
+ value.is_a?(Property) && value.value.is_a?(String) && value.scope.find { |x| x.is_a?(::RDF::Literal) }.present?
397
+ end
398
398
 
399
- # Constructs a CompositeSolrRow object with the language-tagged literal value
400
- # @return [CompositeSolrRow]
401
- def result
402
- CompositeSolrRow.new(
403
- [
404
- calling_mapper.for(Property.new(value.key, value.value)).result,
405
- calling_mapper.for(Property.new("#{value.key}_lang", "eng")).result,
406
- calling_mapper.for(Property.new("#{value.key}_type", "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")).result
407
- ]
408
- )
409
- end
399
+ # Constructs a CompositeSolrRow object with the language-tagged literal value
400
+ # @return [CompositeSolrRow]
401
+ def result
402
+ CompositeSolrRow.new(
403
+ [
404
+ calling_mapper.for(Property.new(value.key, value.value)).result,
405
+ calling_mapper.for(Property.new("#{value.key}_lang", "eng")).result,
406
+ calling_mapper.for(Property.new("#{value.key}_type", "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")).result
407
+ ]
408
+ )
410
409
  end
410
+ end
411
411
 
412
- # Handles casting strings.
413
- class StringPropertyValue < ::Valkyrie::ValueMapper
414
- SolrMapperValue.register(self)
412
+ # Handles casting strings.
413
+ class StringPropertyValue < ::Valkyrie::ValueMapper
414
+ SolrMapperValue.register(self)
415
415
 
416
- # Determines whether or not a Property value is a String
417
- # @param [Object] value
418
- # @return [Boolean]
419
- def self.handles?(value)
420
- value.is_a?(Property) && value.value.is_a?(String)
421
- end
416
+ # Determines whether or not a Property value is a String
417
+ # @param [Object] value
418
+ # @return [Boolean]
419
+ def self.handles?(value)
420
+ value.is_a?(Property) && value.value.is_a?(String)
421
+ end
422
422
 
423
- # Constructs a SolrRow object with the String values and Solr field settings
424
- # @return [SolrRow]
425
- def result
426
- SolrRow.new(key: value.key, fields: fields, values: value.value)
427
- end
423
+ # Constructs a SolrRow object with the String values and Solr field settings
424
+ # @return [SolrRow]
425
+ def result
426
+ SolrRow.new(key: value.key, fields: fields, values: value.value)
427
+ end
428
428
 
429
- # Generates the Solr fields used during the indexing
430
- # String are normally indexed using the following:
431
- # - stored text
432
- # - stored english text
433
- # - stored single string
434
- # - multivalued string
435
- # - stored multivalued text
436
- # - stored multivalued english text
437
- # If the string is greater than 1000 characters in length, it is only indexed as a stored multivalued text
438
- # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
439
- # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
440
- # @return [Array<Symbol>]
441
- def fields
442
- if value.value.length > 1000
443
- [:tsim]
444
- else
445
- [:tsim, :ssim, :tesim, :tsi, :ssi, :tesi]
446
- end
429
+ # Generates the Solr fields used during the indexing
430
+ # String are normally indexed using the following:
431
+ # - stored text
432
+ # - stored english text
433
+ # - stored single string
434
+ # - multivalued string
435
+ # - stored multivalued text
436
+ # - stored multivalued english text
437
+ # If the string is greater than 1000 characters in length, it is only indexed as a stored multivalued text
438
+ # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
439
+ # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
440
+ # @return [Array<Symbol>]
441
+ def fields
442
+ if value.value.length > 1000
443
+ [:tsim]
444
+ else
445
+ [:tsim, :ssim, :tesim, :tsi, :ssi, :tesi]
447
446
  end
448
447
  end
448
+ end
449
449
 
450
- # Handles casting language-typed {RDF::Literal}s
451
- class LiteralPropertyValue < ::Valkyrie::ValueMapper
452
- SolrMapperValue.register(self)
450
+ # Handles casting language-typed {RDF::Literal}s
451
+ class LiteralPropertyValue < ::Valkyrie::ValueMapper
452
+ SolrMapperValue.register(self)
453
453
 
454
- # Determines whether or not a Property value is an RDF literal
455
- # @param [Object] value
456
- # @return [Boolean]
457
- def self.handles?(value)
458
- value.is_a?(Property) && value.value.is_a?(::RDF::Literal)
459
- end
454
+ # Determines whether or not a Property value is an RDF literal
455
+ # @param [Object] value
456
+ # @return [Boolean]
457
+ def self.handles?(value)
458
+ value.is_a?(Property) && value.value.is_a?(::RDF::Literal)
459
+ end
460
460
 
461
- # Constructs a CompositeSolrRow object with the language-tagged literal value
462
- # @return [CompositeSolrRow]
463
- def result
464
- key = value.key
465
- val = value.value
466
- CompositeSolrRow.new(
467
- [
468
- calling_mapper.for(Property.new(key, val.to_s)).result,
469
- calling_mapper.for(Property.new("#{key}_lang", val.language.to_s)).result,
470
- calling_mapper.for(Property.new("#{key}_type", val.datatype.to_s)).result
471
- ]
472
- )
473
- end
461
+ # Constructs a CompositeSolrRow object with the language-tagged literal value
462
+ # @return [CompositeSolrRow]
463
+ def result
464
+ key = value.key
465
+ val = value.value
466
+ CompositeSolrRow.new(
467
+ [
468
+ calling_mapper.for(Property.new(key, val.to_s)).result,
469
+ calling_mapper.for(Property.new("#{key}_lang", val.language.to_s)).result,
470
+ calling_mapper.for(Property.new("#{key}_type", val.datatype.to_s)).result
471
+ ]
472
+ )
474
473
  end
474
+ end
475
475
  end
476
476
  end