valkyrie 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d6a78cd9f5ae0e854c54e19e046d8fd0ff01b43d3049ea58b904ba09d758fce
4
- data.tar.gz: 54175934841fe91f264fb106a4d62a95600078b97311950f81a878527b1d2bf5
3
+ metadata.gz: fe10f5866474217fe3edb3acce93747447fe6de5c8ad582d0be617f0dd93c040
4
+ data.tar.gz: e2210174bf001c9115b14266e388cc2999d0a74c560d8872b554e5c04973eb52
5
5
  SHA512:
6
- metadata.gz: 4d31c229dc71fe0b0be099d624adbb4d72a11d7212144f0b815ff4348be9e73b13e2f6c41b9decf7e90dece3578bcce566deb2c2fcee06f2e4ff2e7174088814
7
- data.tar.gz: cc1b647fcb551eccc4fe0ce670bf1ecea2fbe3d328d5c304f7cffd217191309374272f24f0687609b78ba96db7276c69b24688ef46addfc993e43b94979f864f
6
+ metadata.gz: 72d2fedc30bf650bd3794ab34687a47b071ab41fe1c8b29ebaef582b80616eed7f86aa549bc723976f0655fe9d0e302714294ecfb1a1a2461fb2428f00f4cd7c
7
+ data.tar.gz: 76bc7d38aa82f6e6bcc57e6e1d8bb7f762cc0f62d2f571f8e96d6a0a0fb76efab8f73327c86e239dfd65dc43fbe2d1d0798695b0008a3240bef0f7d08edd6c2d
@@ -0,0 +1,46 @@
1
+ ---
2
+ version: 2
3
+ jobs:
4
+ build:
5
+ machine: true
6
+ steps:
7
+ - checkout
8
+ - run:
9
+ name: Install Docker Compose
10
+ command: |
11
+ curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` > ~/docker-compose
12
+ chmod +x ~/docker-compose
13
+ sudo mv ~/docker-compose /usr/local/bin/docker-compose
14
+ - restore_cache:
15
+ keys:
16
+ - bundle-{{ checksum "Gemfile" }}-{{ checksum "valkyrie.gemspec" }}
17
+ - bundle- # used if checksum fails
18
+ - run: sudo apt-get update && sudo apt-get install -y libpq-dev
19
+ - run:
20
+ name: Install dependencies
21
+ command: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs 4 --retry 3
22
+ - save_cache:
23
+ key: bundle-{{ checksum "Gemfile" }}-{{ checksum "valkyrie.gemspec" }}
24
+ paths:
25
+ - "vendor/bundle"
26
+ - run:
27
+ name: Run Rubocop
28
+ command: bundle exec rake rubocop
29
+ - run:
30
+ name: Run Specs
31
+ command: bundle exec rake docker:spec
32
+ workflows:
33
+ version: 2
34
+ build:
35
+ jobs:
36
+ - build
37
+ nightly:
38
+ triggers:
39
+ - schedule:
40
+ cron: "0 0 * * *"
41
+ filters:
42
+ branches:
43
+ only:
44
+ - master
45
+ jobs:
46
+ - build
@@ -0,0 +1,68 @@
1
+ ---
2
+ version: '3.4'
3
+ volumes:
4
+ fedora:
5
+ db:
6
+ solr_repo:
7
+ solr_index:
8
+ services:
9
+ fedora:
10
+ image: nulib/fcrepo4
11
+ volumes:
12
+ - fedora:/data
13
+ ports:
14
+ - 8986:8080
15
+ db:
16
+ image: healthcheck/postgres:alpine
17
+ volumes:
18
+ - db:/data
19
+ environment:
20
+ - PGDATA=/data
21
+ - POSTGRES_USER=docker
22
+ - POSTGRES_PASSWORD=d0ck3r
23
+ ports:
24
+ - 5433:5432
25
+ solr_repo:
26
+ image: solr:7.2-alpine
27
+ ports:
28
+ - 8983:8983
29
+ volumes:
30
+ - solr_repo:/opt/solr/server/solr/mycores
31
+ - "../../solr:/solr_config"
32
+ entrypoint:
33
+ - docker-entrypoint.sh
34
+ - solr-precreate
35
+ - blacklight-core
36
+ - "/solr_config/config"
37
+ healthcheck:
38
+ test:
39
+ - CMD
40
+ - wget
41
+ - "-O"
42
+ - "/dev/null"
43
+ - http://localhost:8983/solr/
44
+ interval: 30s
45
+ timeout: 5s
46
+ retries: 3
47
+ solr_index:
48
+ image: solr:7.2-alpine
49
+ ports:
50
+ - 8987:8983
51
+ volumes:
52
+ - solr_index:/opt/solr/server/solr/mycores
53
+ - "../../solr:/solr_config"
54
+ entrypoint:
55
+ - docker-entrypoint.sh
56
+ - solr-precreate
57
+ - hydra-dev
58
+ - "/solr_config/config"
59
+ healthcheck:
60
+ test:
61
+ - CMD
62
+ - wget
63
+ - "-O"
64
+ - "/dev/null"
65
+ - http://localhost:8983/solr/
66
+ interval: 30s
67
+ timeout: 5s
68
+ retries: 3
@@ -0,0 +1,68 @@
1
+ ---
2
+ version: '3.4'
3
+ volumes:
4
+ fedora:
5
+ db:
6
+ solr_repo:
7
+ solr_index:
8
+ services:
9
+ fedora:
10
+ image: nulib/fcrepo4
11
+ volumes:
12
+ - fedora:/data
13
+ ports:
14
+ - 8988:8080
15
+ db:
16
+ image: healthcheck/postgres:alpine
17
+ volumes:
18
+ - db:/data
19
+ environment:
20
+ - PGDATA=/data
21
+ - POSTGRES_USER=docker
22
+ - POSTGRES_PASSWORD=d0ck3r
23
+ ports:
24
+ - 5434:5432
25
+ solr_repo:
26
+ image: solr:7.2-alpine
27
+ ports:
28
+ - 8984:8983
29
+ volumes:
30
+ - solr_repo:/opt/solr/server/solr/mycores
31
+ - "../../solr:/solr_config"
32
+ entrypoint:
33
+ - docker-entrypoint.sh
34
+ - solr-precreate
35
+ - blacklight-core-test
36
+ - "/solr_config/config"
37
+ healthcheck:
38
+ test:
39
+ - CMD
40
+ - wget
41
+ - "-O"
42
+ - "/dev/null"
43
+ - http://localhost:8983/solr/
44
+ interval: 30s
45
+ timeout: 5s
46
+ retries: 3
47
+ solr_index:
48
+ image: solr:7.2-alpine
49
+ ports:
50
+ - 8985:8983
51
+ volumes:
52
+ - solr_index:/opt/solr/server/solr/mycores
53
+ - "../../solr:/solr_config"
54
+ entrypoint:
55
+ - docker-entrypoint.sh
56
+ - solr-precreate
57
+ - hydra-test
58
+ - "/solr_config/config"
59
+ healthcheck:
60
+ test:
61
+ - CMD
62
+ - wget
63
+ - "-O"
64
+ - "/dev/null"
65
+ - http://localhost:8983/solr/
66
+ interval: 30s
67
+ timeout: 5s
68
+ retries: 3
@@ -1,3 +1,16 @@
1
+ # v1.1.0 2018-05-08
2
+
3
+ ## Changes since last release
4
+
5
+ * Added `find_by_alternate_identifier` query.
6
+ [stkenny](https://github.com/stkenny)
7
+ * Added Docker environment for development.
8
+ [mbklein](https://github.com/mbklein)
9
+ * Fixed README documentation.
10
+ [revgum](https://github.com/revgum)
11
+ * Deprecated `Valkyrie::Persistence::Fedora::PermissiveSchema.references`
12
+ * Deprecated `Valkyrie::Persistence::Fedora::PermissiveSchema.alternate_ids`
13
+
1
14
  # v1.0.0 2018-03-23
2
15
 
3
16
  ## Changes since last release
data/README.md CHANGED
@@ -35,7 +35,7 @@ instance with a short name that can be used to refer to it in your application:
35
35
  require 'valkyrie'
36
36
  Rails.application.config.to_prepare do
37
37
  Valkyrie::MetadataAdapter.register(
38
- Valkyrie::Persistence::Postgres::MetadataAdapter,
38
+ Valkyrie::Persistence::Postgres::MetadataAdapter.new,
39
39
  :postgres
40
40
  )
41
41
 
@@ -83,20 +83,20 @@ A sample configuration file that configures your application to use different ad
83
83
 
84
84
  ```
85
85
  development:
86
- adapter: postgres
86
+ metadata_adapter: postgres
87
87
  storage_adapter: disk
88
88
 
89
89
  test:
90
- adapter: memory
90
+ metadata_adapter: memory
91
91
  storage_adapter: memory
92
92
 
93
93
  production:
94
- adapter: postgres
94
+ metadata_adapter: postgres
95
95
  storage_adapter: fedora
96
96
  ```
97
97
 
98
98
  For each environment, you must set two values:
99
- * `adapter` is the store where Valkyrie will put the metadata
99
+ * `metadata_adapter` is the store where Valkyrie will put the metadata
100
100
  * `storage_adapter` is the store where Valkyrie will put the files
101
101
 
102
102
  The values are the short names used in your initializer.
@@ -120,7 +120,7 @@ end
120
120
 
121
121
  #### Work Types Generator
122
122
 
123
- To create a custom Valkyrie model in your application, you can use the Rails generator. For example, to
123
+ To create a custom Valkyrie model in your application, you can use the Rails generator. For example, to
124
124
  generate a model named `FooBar` with an unordered `title` field and an ordered `member_ids` field:
125
125
 
126
126
  ```
@@ -136,15 +136,18 @@ rails generate valkyrie:resource Foo/Bar title member_ids:array
136
136
  ### Read and Write Data
137
137
 
138
138
  ```
139
+ # initialize a metadata adapter
140
+ adapter = Valkyrie::MetadataAdapter.find(:postgres)
141
+
139
142
  # create an object
140
143
  object1 = MyModel.new title: 'My Cool Object', authors: ['Jones, Alice', 'Smith, Bob']
141
- object1 = Persister.save(model: object1)
144
+ object1 = adapter.persister.save(resource: object1)
142
145
 
143
146
  # load an object from the database
144
- object2 = QueryService.find_by(id: object1.id)
147
+ object2 = adapter.query_service.find_by(id: object1.id)
145
148
 
146
149
  # load all objects
147
- objects = QueryService.find_all
150
+ objects = adapter.query_service.find_all
148
151
 
149
152
  # load all MyModel objects
150
153
  Valkyrie.config.metadata_adapter.query_service.find_all_of_model(model: MyModel)
@@ -153,17 +156,53 @@ Valkyrie.config.metadata_adapter.query_service.find_all_of_model(model: MyModel)
153
156
 
154
157
  ## Installing a Development environment
155
158
 
156
- ### External Requirements
159
+ ### Without Docker
160
+
161
+ #### External Requirements
157
162
  * PostgreSQL with the uuid-ossp extension.
158
163
  * Note: Enabling uuid-ossp requires database superuser privileges.
159
164
  * From `psql`: `alter user [username] with superuser;`
160
165
 
161
- ### To run the test suite
166
+ #### To run the test suite
162
167
  1. Start Solr and Fedora servers for testing with `rake server:test`
163
168
  1. Run `rake db:create` (First time only)
164
169
  1. Run `rake db:migrate`
170
+
171
+ ### With Docker
172
+
173
+ #### External Requirements
174
+ * [Docker](https://store.docker.com/search?offering=community&type=edition) version >= 17.09.0
175
+ *
176
+ ### Dependency Setup (Mac OSX)
177
+
178
+ 1. `brew install docker`
179
+ 1. `brew install docker-machine`
180
+ 1. `brew install docker-compose`
181
+
182
+ ### Starting Docker (Mac OSX)
183
+
184
+ 1. `docker-machine create default`
185
+ 1. `docker-machine start default`
186
+ 1. `eval "$(docker-machine env)"
187
+
188
+ #### Starting the development mode dependencies
189
+ 1. Start Solr, Fedora, and PostgreSQL with `rake docker:dev:daemon` (or `rake docker:dev:up` in a separate shell to run them in the foreground)
190
+ 1. Run `rake db:create db:migrate` to initialize the database
191
+ 1. Develop!
192
+ 1. Run `rake docker:dev:down` to stop the server stack
193
+ * Development servers maintain data between runs. To clean them out, run `rake docker:dev:clean`
194
+
195
+ #### To run the test suite with all dependencies in one go
196
+ 1. `rake docker:spec`
197
+
198
+ #### To run the test suite manually
199
+ 1. Start Solr, Fedora, and PostgreSQL with `rake docker:test:daemon` (or `rake docker:test:up` in a separate shell to run them in the foreground)
200
+ 1. Run `rake db:create db:migrate` to initialize the database
165
201
  1. Run the gem's RSpec test suite with `rspec spec` or `rake`
202
+ 1. Run `rake docker:test:down` to stop the server stack
203
+ * The test stack cleans up after itself on exit.
166
204
 
205
+ The development and test stacks use fully contained virtual volumes and bind all services to different ports, so they can be running at the same time without issue.
167
206
 
168
207
  ## License
169
208
 
data/Rakefile CHANGED
@@ -5,7 +5,9 @@ require 'yaml'
5
5
  require 'config/database_connection'
6
6
  require 'active_record'
7
7
  require 'rubocop/rake_task'
8
- load "tasks/dev.rake"
8
+ load 'tasks/dev.rake'
9
+ load 'tasks/docker.rake'
10
+
9
11
  RSpec::Core::RakeTask.new(:spec)
10
12
 
11
13
  task default: :spec
@@ -25,7 +27,7 @@ namespace :db do
25
27
  end
26
28
 
27
29
  task configuration: :environment do
28
- @config = YAML.safe_load(ERB.new(File.read("db/config.yml")).result)[DATABASE_ENV]
30
+ @config = YAML.safe_load(ERB.new(File.read("db/config.yml")).result, [], [], true)[DATABASE_ENV]
29
31
  end
30
32
 
31
33
  task configure_connection: :configuration do
@@ -1,17 +1,27 @@
1
- development: &default
1
+ <% local = File.exist?('/tmp/.s.PGSQL.5432') && File.stat('/tmp/.s.PGSQL.5432').socket? %>
2
+ default: &default
2
3
  adapter: postgresql
3
- database: Valkyrie_gem_development
4
4
  encoding: utf8
5
5
  min_messages: warning
6
6
  pool: <%= Integer(ENV.fetch("DB_POOL", 5)) %>
7
7
  reaping_frequency: <%= Integer(ENV.fetch("DB_REAPING_FREQUENCY", 10)) %>
8
8
  timeout: 5000
9
+ <% unless local %>
10
+ host: localhost
11
+ username: docker
12
+ password: d0ck3r
13
+ <% end %>
14
+
15
+ development:
16
+ <<: *default
17
+ database: Valkyrie_gem_development
18
+ <% unless local %>
19
+ port: 5433
20
+ <% end %>
9
21
 
10
22
  test:
11
- adapter: postgresql
12
- encoding: utf8
13
- min_messages: warning
14
- pool: <%= Integer(ENV.fetch("DB_POOL", 5)) %>
15
- reaping_frequency: <%= Integer(ENV.fetch("DB_REAPING_FREQUENCY", 10)) %>
16
- timeout: 5000
23
+ <<: *default
17
24
  database: Valkyrie_gem_test
25
+ <% unless local %>
26
+ port: 5434
27
+ <% end %>
@@ -7,7 +7,7 @@ module DatabaseConnection
7
7
  # Ref https://github.com/puma/puma#clustered-mode
8
8
  ActiveSupport.on_load(:active_record) do
9
9
  ::ActiveRecord::Base.connection_pool.disconnect! if ::ActiveRecord::Base.connected?
10
- ::ActiveRecord::Base.configurations = YAML.safe_load(ERB.new(File.read("db/config.yml")).result) || {}
10
+ ::ActiveRecord::Base.configurations = YAML.safe_load(ERB.new(File.read("db/config.yml")).result, [], [], true) || {}
11
11
  config = ::ActiveRecord::Base.configurations[env.to_s]
12
12
  ::ActiveRecord::Base.establish_connection(config)
13
13
  end
@@ -22,11 +22,27 @@ module Valkyrie::Persistence::Fedora
22
22
  uri_for(:id)
23
23
  end
24
24
 
25
+ # @deprecated Please use {.uri_for} instead
26
+ def self.alternate_ids
27
+ warn "[DEPRECATION] `alternate_ids` is deprecated and will be removed in the next major release. " \
28
+ "It was never used internally - please use `uri_for(:alternate_ids)` " \
29
+ "Called from #{Gem.location_of_caller.join(':')}"
30
+ uri_for(:alternate_ids)
31
+ end
32
+
25
33
  # @return [RDF::URI]
26
34
  def self.member_ids
27
35
  uri_for(:member_ids)
28
36
  end
29
37
 
38
+ # @deprecated Please use {.uri_for} instead
39
+ def self.references
40
+ warn "[DEPRECATION] `references` is deprecated and will be removed in the next major release. " \
41
+ "It was never used internally - please use `uri_for(:references)` " \
42
+ "Called from #{Gem.location_of_caller.join(':')}"
43
+ uri_for(:references)
44
+ end
45
+
30
46
  # @return [RDF::URI]
31
47
  def self.valkyrie_bool
32
48
  uri_for(:valkyrie_bool)
@@ -3,6 +3,7 @@ module Valkyrie::Persistence::Fedora
3
3
  # Persister for Fedora MetadataAdapter.
4
4
  class Persister
5
5
  require 'valkyrie/persistence/fedora/persister/resource_factory'
6
+ require 'valkyrie/persistence/fedora/persister/alternate_identifier'
6
7
  attr_reader :adapter
7
8
  delegate :connection, :base_path, :resource_factory, to: :adapter
8
9
  def initialize(adapter:)
@@ -16,14 +17,17 @@ module Valkyrie::Persistence::Fedora
16
17
  resource.updated_at ||= Time.current
17
18
  ensure_multiple_values!(resource)
18
19
  orm = resource_factory.from_resource(resource: resource)
20
+ alternate_resources = find_or_create_alternate_ids(resource)
21
+
19
22
  if !orm.new? || resource.id
20
- orm.update do |req|
21
- req.headers["Prefer"] = "handling=lenient; received=\"minimal\""
22
- end
23
+ cleanup_alternate_resources(resource) if alternate_resources
24
+ orm.update { |req| req.headers["Prefer"] = "handling=lenient; received=\"minimal\"" }
23
25
  else
24
26
  orm.create
25
27
  end
26
- resource_factory.to_resource(object: orm)
28
+ persisted_resource = resource_factory.to_resource(object: orm)
29
+
30
+ alternate_resources ? save_reference_to_resource(persisted_resource, alternate_resources) : persisted_resource
27
31
  end
28
32
 
29
33
  # (see Valkyrie::Persistence::Memory::Persister#save_all)
@@ -35,8 +39,15 @@ module Valkyrie::Persistence::Fedora
35
39
 
36
40
  # (see Valkyrie::Persistence::Memory::Persister#delete)
37
41
  def delete(resource:)
42
+ if resource.try(:alternate_ids)
43
+ resource.alternate_ids.each do |alternate_identifier|
44
+ adapter.persister.delete(resource: adapter.query_service.find_by(id: alternate_identifier))
45
+ end
46
+ end
47
+
38
48
  orm = resource_factory.from_resource(resource: resource)
39
49
  orm.delete
50
+
40
51
  resource
41
52
  end
42
53
 
@@ -60,10 +71,41 @@ module Valkyrie::Persistence::Fedora
60
71
  private
61
72
 
62
73
  def ensure_multiple_values!(resource)
63
- bad_keys = resource.attributes.except(:internal_resource, :created_at, :updated_at, :new_record, :id).select do |_k, v|
74
+ bad_keys = resource.attributes.except(:internal_resource, :created_at, :updated_at, :new_record, :id, :references).select do |_k, v|
64
75
  !v.nil? && !v.is_a?(Array)
65
76
  end
66
77
  raise ::Valkyrie::Persistence::UnsupportedDatatype, "#{resource}: #{bad_keys.keys} have non-array values, which can not be persisted by Valkyrie. Cast to arrays." unless bad_keys.keys.empty?
67
78
  end
79
+
80
+ def find_or_create_alternate_ids(resource)
81
+ return nil unless resource.try(:alternate_ids)
82
+
83
+ resource.alternate_ids.map do |alternate_identifier|
84
+ begin
85
+ adapter.query_service.find_by(id: alternate_identifier)
86
+ rescue ::Valkyrie::Persistence::ObjectNotFoundError
87
+ alternate_resource = ::Valkyrie::Persistence::Fedora::AlternateIdentifier.new(id: alternate_identifier)
88
+ adapter.persister.save(resource: alternate_resource)
89
+ end
90
+ end
91
+ end
92
+
93
+ def cleanup_alternate_resources(updated_resource)
94
+ persisted_resource = adapter.query_service.find_by(id: updated_resource.id)
95
+ removed_identifiers = persisted_resource.alternate_ids - updated_resource.alternate_ids
96
+
97
+ removed_identifiers.each do |removed_id|
98
+ adapter.persister.delete(resource: adapter.query_service.find_by(id: removed_id))
99
+ end
100
+ end
101
+
102
+ def save_reference_to_resource(resource, alternate_resources)
103
+ alternate_resources.each do |alternate_resource|
104
+ alternate_resource.references = resource.id
105
+ adapter.persister.save(resource: alternate_resource)
106
+ end
107
+
108
+ resource
109
+ end
68
110
  end
69
111
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require 'valkyrie/resource'
3
+ require 'valkyrie/types'
4
+
5
+ module Valkyrie::Persistence::Fedora
6
+ class AlternateIdentifier < ::Valkyrie::Resource
7
+ attribute :id, ::Valkyrie::Types::ID.optional
8
+ attribute :references, ::Valkyrie::Types::ID.optional
9
+ end
10
+ end
@@ -10,15 +10,19 @@ module Valkyrie::Persistence::Fedora
10
10
 
11
11
  # (see Valkyrie::Persistence::Memory::QueryService#find_by)
12
12
  def find_by(id:)
13
- id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
14
13
  validate_id(id)
15
14
  uri = adapter.id_to_uri(id)
16
- begin
17
- resource = Ldp::Resource.for(connection, uri, connection.get(uri))
18
- resource_factory.to_resource(object: resource)
19
- rescue ::Ldp::Gone, ::Ldp::NotFound
20
- raise ::Valkyrie::Persistence::ObjectNotFoundError
21
- end
15
+
16
+ resource_from_uri(uri)
17
+ end
18
+
19
+ # (see Valkyrie::Persistence::Memory::QueryService#find_by_alternate_identifier)
20
+ def find_by_alternate_identifier(alternate_identifier:)
21
+ validate_id(alternate_identifier)
22
+ uri = adapter.id_to_uri(alternate_identifier)
23
+ alternate_id = resource_from_uri(uri).references
24
+
25
+ find_by(id: alternate_id)
22
26
  end
23
27
 
24
28
  # (see Valkyrie::Persistence::Memory::QueryService#find_many_by_ids)
@@ -113,9 +117,17 @@ module Valkyrie::Persistence::Fedora
113
117
  private
114
118
 
115
119
  def validate_id(id)
120
+ id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
116
121
  raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
117
122
  end
118
123
 
124
+ def resource_from_uri(uri)
125
+ resource = Ldp::Resource.for(connection, uri, connection.get(uri))
126
+ resource_factory.to_resource(object: resource)
127
+ rescue ::Ldp::Gone, ::Ldp::NotFound
128
+ raise ::Valkyrie::Persistence::ObjectNotFoundError
129
+ end
130
+
119
131
  def ensure_persisted(resource)
120
132
  raise ArgumentError, 'resource is not saved' unless resource.persisted?
121
133
  end
@@ -24,6 +24,17 @@ module Valkyrie::Persistence::Memory
24
24
  cache[id] || raise(::Valkyrie::Persistence::ObjectNotFoundError)
25
25
  end
26
26
 
27
+ # @param alternate_identifier [Valkyrie::ID] The alternate identifier to query for.
28
+ # @raise [Valkyrie::Persistence::ObjectNotFoundError] Raised when the alternate identifier
29
+ # isn't in the persistence backend.
30
+ # @raise [ArgumentError] Raised when alternate identifier is not a String or a Valkyrie::ID
31
+ # @return [Valkyrie::Resource] The object being searched for.
32
+ def find_by_alternate_identifier(alternate_identifier:)
33
+ alternate_identifier = Valkyrie::ID.new(alternate_identifier.to_s) if alternate_identifier.is_a?(String)
34
+ validate_id(alternate_identifier)
35
+ cache.select { |_key, resource| resource['alternate_ids'].include?(alternate_identifier) }.values.first || raise(::Valkyrie::Persistence::ObjectNotFoundError)
36
+ end
37
+
27
38
  # @param ids [Array<Valkyrie::ID, String>] The IDs to query for.
28
39
  # @raise [ArgumentError] Raised when any ID is not a String or a Valkyrie::ID
29
40
  # @return [Array<Valkyrie::Resource>] All requested objects that were found
@@ -37,6 +37,14 @@ module Valkyrie::Persistence::Postgres
37
37
  raise Valkyrie::Persistence::ObjectNotFoundError
38
38
  end
39
39
 
40
+ # (see Valkyrie::Persistence::Memory::QueryService#find_by_alternate_identifier)
41
+ def find_by_alternate_identifier(alternate_identifier:)
42
+ alternate_identifier = Valkyrie::ID.new(alternate_identifier.to_s) if alternate_identifier.is_a?(String)
43
+ validate_id(alternate_identifier)
44
+ internal_array = "{\"alternate_ids\": [{\"id\": \"#{alternate_identifier}\"}]}"
45
+ run_query(find_inverse_references_query, internal_array).first || raise(Valkyrie::Persistence::ObjectNotFoundError)
46
+ end
47
+
40
48
  # (see Valkyrie::Persistence::Memory::QueryService#find_many_by_ids)
41
49
  def find_many_by_ids(ids:)
42
50
  ids.map! do |id|
@@ -48,8 +56,6 @@ module Valkyrie::Persistence::Postgres
48
56
  orm_class.where(id: ids).map do |orm_resource|
49
57
  resource_factory.to_resource(object: orm_resource)
50
58
  end
51
- rescue ActiveRecord::RecordNotFound
52
- raise Valkyrie::Persistence::ObjectNotFoundError
53
59
  end
54
60
 
55
61
  # (see Valkyrie::Persistence::Memory::QueryService#find_members)
@@ -6,6 +6,7 @@ module Valkyrie::Persistence::Solr
6
6
  require 'valkyrie/persistence/solr/queries/default_paginator'
7
7
  require 'valkyrie/persistence/solr/queries/find_all_query'
8
8
  require 'valkyrie/persistence/solr/queries/find_by_id_query'
9
+ require 'valkyrie/persistence/solr/queries/find_by_alternate_identifier_query'
9
10
  require 'valkyrie/persistence/solr/queries/find_many_by_ids_query'
10
11
  require 'valkyrie/persistence/solr/queries/find_inverse_references_query'
11
12
  require 'valkyrie/persistence/solr/queries/find_members_query'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ module Valkyrie::Persistence::Solr::Queries
3
+ # Responsible for returning a single resource identified by an ID.
4
+ class FindByAlternateIdentifierQuery
5
+ attr_reader :connection, :resource_factory
6
+ attr_writer :alternate_identifier
7
+ def initialize(alternate_identifier, connection:, resource_factory:)
8
+ @alternate_identifier = alternate_identifier
9
+ @connection = connection
10
+ @resource_factory = resource_factory
11
+ end
12
+
13
+ def run
14
+ raise ::Valkyrie::Persistence::ObjectNotFoundError unless resource
15
+ resource_factory.to_resource(object: resource)
16
+ end
17
+
18
+ def alternate_identifier
19
+ @alternate_identifier.to_s
20
+ end
21
+
22
+ def resource
23
+ connection.get("select", params: { q: "alternate_ids_ssim:\"id-#{alternate_identifier}\"", fl: "*", rows: 1 })["response"]["docs"].first
24
+ end
25
+ end
26
+ end
@@ -18,6 +18,13 @@ module Valkyrie::Persistence::Solr
18
18
  Valkyrie::Persistence::Solr::Queries::FindByIdQuery.new(id, connection: connection, resource_factory: resource_factory).run
19
19
  end
20
20
 
21
+ # (see Valkyrie::Persistence::Memory::QueryService#find_by_alternate_identifier)
22
+ def find_by_alternate_identifier(alternate_identifier:)
23
+ alternate_identifier = Valkyrie::ID.new(alternate_identifier.to_s) if alternate_identifier.is_a?(String)
24
+ validate_id(alternate_identifier)
25
+ Valkyrie::Persistence::Solr::Queries::FindByAlternateIdentifierQuery.new(alternate_identifier, connection: connection, resource_factory: resource_factory).run
26
+ end
27
+
21
28
  # (see Valkyrie::Persistence::Memory::QueryService#find_many_by_ids)
22
29
  def find_many_by_ids(ids:)
23
30
  ids.map! do |id|
@@ -39,7 +39,7 @@ module Valkyrie::Persistence::Solr
39
39
  end
40
40
 
41
41
  def ensure_multiple_values!(resource)
42
- bad_keys = resource.attributes.except(:internal_resource, :created_at, :updated_at, :new_record, :id).select do |_k, v|
42
+ bad_keys = resource.attributes.except(:internal_resource, :alternate_ids, :created_at, :updated_at, :new_record, :id).select do |_k, v|
43
43
  !v.nil? && !v.is_a?(Array)
44
44
  end
45
45
  raise ::Valkyrie::Persistence::UnsupportedDatatype, "#{resource}: #{bad_keys.keys} have non-array values, which can not be persisted by Valkyrie. Cast to arrays." unless bad_keys.keys.empty?
@@ -5,6 +5,7 @@ RSpec.shared_examples 'a Valkyrie query provider' do
5
5
  defined? adapter
6
6
  class CustomResource < Valkyrie::Resource
7
7
  attribute :id, Valkyrie::Types::ID.optional
8
+ attribute :alternate_ids, Valkyrie::Types::Array
8
9
  attribute :title
9
10
  attribute :member_ids, Valkyrie::Types::Array
10
11
  attribute :a_member_of
@@ -25,6 +26,7 @@ RSpec.shared_examples 'a Valkyrie query provider' do
25
26
  it { is_expected.to respond_to(:find_all).with(0).arguments }
26
27
  it { is_expected.to respond_to(:find_all_of_model).with_keywords(:model) }
27
28
  it { is_expected.to respond_to(:find_by).with_keywords(:id) }
29
+ it { is_expected.to respond_to(:find_by_alternate_identifier).with_keywords(:alternate_identifier) }
28
30
  it { is_expected.to respond_to(:find_many_by_ids).with_keywords(:ids) }
29
31
  it { is_expected.to respond_to(:find_members).with_keywords(:resource, :model) }
30
32
  it { is_expected.to respond_to(:find_references_by).with_keywords(:resource, :property) }
@@ -74,6 +76,44 @@ RSpec.shared_examples 'a Valkyrie query provider' do
74
76
  end
75
77
  end
76
78
 
79
+ describe ".find_by_alternate_identifier" do
80
+ it "returns a resource by alternate identifier or string representation of an alternate identifier" do
81
+ resource = resource_class.new
82
+ resource.alternate_ids = [Valkyrie::ID.new('p9s0xfj')]
83
+ resource = persister.save(resource: resource)
84
+
85
+ found = query_service.find_by_alternate_identifier(alternate_identifier: resource.alternate_ids.first)
86
+ expect(found.id).to eq resource.id
87
+ expect(found).to be_persisted
88
+
89
+ found = query_service.find_by_alternate_identifier(alternate_identifier: resource.alternate_ids.first.to_s)
90
+ expect(found.id).to eq resource.id
91
+ expect(found).to be_persisted
92
+ end
93
+
94
+ it "returns a Valkyrie::Persistence::ObjectNotFoundError for a non-found alternate identifier" do
95
+ expect { query_service.find_by_alternate_identifier(alternate_identifier: Valkyrie::ID.new("123123123")) }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
96
+ end
97
+
98
+ it 'raises an error if the alternate identifier is not a Valkyrie::ID or a string' do
99
+ expect { query_service.find_by_alternate_identifier(alternate_identifier: 123) }.to raise_error ArgumentError
100
+ end
101
+
102
+ it 'can have multiple alternate identifiers' do
103
+ resource = resource_class.new
104
+ resource.alternate_ids = [Valkyrie::ID.new('p9s0xfj'), Valkyrie::ID.new('jks0xfj')]
105
+ resource = persister.save(resource: resource)
106
+
107
+ found = query_service.find_by_alternate_identifier(alternate_identifier: resource.alternate_ids.first)
108
+ expect(found.id).to eq resource.id
109
+ expect(found).to be_persisted
110
+
111
+ found = query_service.find_by_alternate_identifier(alternate_identifier: resource.alternate_ids.last)
112
+ expect(found.id).to eq resource.id
113
+ expect(found).to be_persisted
114
+ end
115
+ end
116
+
77
117
  describe ".find_many_by_ids" do
78
118
  let!(:resource) { persister.save(resource: resource_class.new) }
79
119
  let!(:resource2) { persister.save(resource: resource_class.new) }
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Valkyrie
3
- VERSION = "1.0.0"
3
+ VERSION = "1.1.0"
4
4
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ require 'valkyrie'
3
+
4
+ if Rails.env.development? || Rails.env.test?
5
+ begin
6
+ require 'docker/stack/rake_task'
7
+
8
+ def get_named_task(task_name)
9
+ Rake::Task[task_name]
10
+ rescue RuntimeError
11
+ nil
12
+ end
13
+
14
+ namespace :docker do
15
+ namespace(:dev) { Docker::Stack::RakeTask.load_tasks }
16
+ namespace(:test) { Docker::Stack::RakeTask.load_tasks(force_env: 'test', cleanup: true) }
17
+
18
+ desc 'Spin up test stack and run specs'
19
+ task :spec do
20
+ Rails.env = 'test'
21
+ Docker::Stack::Controller.new(project: 'valkyrie', cleanup: true).with_containers do
22
+ Rake::Task['db:create'].invoke
23
+ Rake::Task['db:migrate'].invoke
24
+ Rake::Task['spec'].invoke
25
+ end
26
+ end
27
+ end
28
+ rescue LoadError
29
+ Rails.logger.warn 'Docker rake tasks not loaded.'
30
+ end
31
+ end
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_dependency 'dry-struct'
23
23
  spec.add_dependency 'draper'
24
24
  spec.add_dependency 'activemodel'
25
- spec.add_dependency 'dry-types'
25
+ spec.add_dependency 'dry-types', '~> 0.12.0'
26
26
  spec.add_dependency 'rdf'
27
27
  spec.add_dependency 'active-fedora'
28
28
  spec.add_dependency 'activesupport'
@@ -49,4 +49,5 @@ Gem::Specification.new do |spec|
49
49
  spec.add_development_dependency 'yard'
50
50
  spec.add_development_dependency 'solr_wrapper'
51
51
  spec.add_development_dependency 'fcrepo_wrapper'
52
+ spec.add_development_dependency 'docker-stack', '~> 0.2.6'
52
53
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: valkyrie
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Trey Pendragon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-23 00:00:00.000000000 Z
11
+ date: 2018-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-struct
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: dry-types
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 0.12.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 0.12.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rdf
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -388,6 +388,20 @@ dependencies:
388
388
  - - ">="
389
389
  - !ruby/object:Gem::Version
390
390
  version: '0'
391
+ - !ruby/object:Gem::Dependency
392
+ name: docker-stack
393
+ requirement: !ruby/object:Gem::Requirement
394
+ requirements:
395
+ - - "~>"
396
+ - !ruby/object:Gem::Version
397
+ version: 0.2.6
398
+ type: :development
399
+ prerelease: false
400
+ version_requirements: !ruby/object:Gem::Requirement
401
+ requirements:
402
+ - - "~>"
403
+ - !ruby/object:Gem::Version
404
+ version: 0.2.6
391
405
  description:
392
406
  email:
393
407
  - tpendragon@princeton.edu
@@ -395,7 +409,10 @@ executables: []
395
409
  extensions: []
396
410
  extra_rdoc_files: []
397
411
  files:
412
+ - ".circleci/config.yml"
398
413
  - ".ctags"
414
+ - ".docker-stack/valkyrie-development/docker-compose.yml"
415
+ - ".docker-stack/valkyrie-test/docker-compose.yml"
399
416
  - ".gitignore"
400
417
  - ".rspec"
401
418
  - ".rubocop.yml"
@@ -410,7 +427,6 @@ files:
410
427
  - bin/rspec
411
428
  - bin/setup
412
429
  - browserslist
413
- - circle.yml
414
430
  - config/fedora.yml
415
431
  - config/valkyrie.yml
416
432
  - db/config.yml
@@ -446,6 +462,7 @@ files:
446
462
  - lib/valkyrie/persistence/fedora/ordered_reader.rb
447
463
  - lib/valkyrie/persistence/fedora/permissive_schema.rb
448
464
  - lib/valkyrie/persistence/fedora/persister.rb
465
+ - lib/valkyrie/persistence/fedora/persister/alternate_identifier.rb
449
466
  - lib/valkyrie/persistence/fedora/persister/model_converter.rb
450
467
  - lib/valkyrie/persistence/fedora/persister/orm_converter.rb
451
468
  - lib/valkyrie/persistence/fedora/persister/resource_factory.rb
@@ -472,6 +489,7 @@ files:
472
489
  - lib/valkyrie/persistence/solr/queries.rb
473
490
  - lib/valkyrie/persistence/solr/queries/default_paginator.rb
474
491
  - lib/valkyrie/persistence/solr/queries/find_all_query.rb
492
+ - lib/valkyrie/persistence/solr/queries/find_by_alternate_identifier_query.rb
475
493
  - lib/valkyrie/persistence/solr/queries/find_by_id_query.rb
476
494
  - lib/valkyrie/persistence/solr/queries/find_inverse_references_query.rb
477
495
  - lib/valkyrie/persistence/solr/queries/find_many_by_ids_query.rb
@@ -519,6 +537,7 @@ files:
519
537
  - solr/config/xslt/luke.xsl
520
538
  - solr/solr.xml
521
539
  - tasks/dev.rake
540
+ - tasks/docker.rake
522
541
  - valkyrie.gemspec
523
542
  homepage:
524
543
  licenses: []
data/circle.yml DELETED
@@ -1,17 +0,0 @@
1
- machine:
2
- ruby:
3
- version: 2.3.1
4
- services:
5
- - redis
6
- general:
7
- artifacts:
8
- - "tmp/capybara"
9
- dependencies:
10
- post:
11
- - bundle exec rake rubocop
12
- - bundle exec rake server:test:
13
- background: true
14
- - bin/jetty_wait
15
- notify:
16
- webhooks:
17
- - url: https://coveralls.io/webhook?repo_token=c3AnaQOFVYYTitAR1w6ySNScXSIfLQwN4