valkyrie-sequel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +33 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +22 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/LICENSE +14 -0
- data/README.md +28 -0
- data/Rakefile +30 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/db/migrations/.gitkeep +0 -0
- data/db/migrations/001_create_orm_resources.rb +19 -0
- data/lib/sequel/extensions/oj_parser.rb +11 -0
- data/lib/valkyrie/sequel/metadata_adapter.rb +63 -0
- data/lib/valkyrie/sequel/persister.rb +151 -0
- data/lib/valkyrie/sequel/query_service.rb +205 -0
- data/lib/valkyrie/sequel/resource_factory/orm_converter.rb +59 -0
- data/lib/valkyrie/sequel/resource_factory/resource_converter.rb +79 -0
- data/lib/valkyrie/sequel/resource_factory.rb +24 -0
- data/lib/valkyrie/sequel/version.rb +6 -0
- data/lib/valkyrie/sequel.rb +16 -0
- data/valkyrie-sequel.gemspec +36 -0
- metadata +234 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 147de175c6560bcb1836553dd0c5c3e94908c5f8
|
4
|
+
data.tar.gz: e48b68150de3a5ed8efb8ac4064866db769227ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 546160cf2ab0d62a4e2196bc8f390510999cc8e2e9e74e6aa3f483eec6c2f236b45103361d37c31f98d84cf4a45fbf4dd98cbe6cf389ad20aefe594eb7326a6f
|
7
|
+
data.tar.gz: 396c775f741c4d972e372c5400cd9f23236dac76c983e5d3183553726c745e3bf30be4dbf4dbb17e456d1ee29fbc8eaa551d0e3dc8f7d929e5bddd0629ab688c
|
@@ -0,0 +1,33 @@
|
|
1
|
+
---
|
2
|
+
version: 2
|
3
|
+
jobs:
|
4
|
+
build:
|
5
|
+
docker:
|
6
|
+
- image: circleci/ruby:2.3.7-node-browsers
|
7
|
+
environment:
|
8
|
+
RAILS_ENV: test
|
9
|
+
DB_HOST: localhost
|
10
|
+
DB_USERNAME: valkyrie_sequel
|
11
|
+
DB_PASSWORD: ""
|
12
|
+
DB_DATABASE: "valkyrie_sequel_test"
|
13
|
+
- image: postgres:10.3-alpine
|
14
|
+
environment:
|
15
|
+
POSTGRES_USER: valkyrie_sequel
|
16
|
+
POSTGRES_DB: valkyrie_sequel_test
|
17
|
+
POSTGRES_PASSWORD: ""
|
18
|
+
steps:
|
19
|
+
- checkout
|
20
|
+
# Restore Cached Dependencies
|
21
|
+
- type: cache-restore
|
22
|
+
name: Restore bundle cache
|
23
|
+
key: valkyrie-sequel-{{ checksum "Gemfile" }}
|
24
|
+
# Bundle install dependencies
|
25
|
+
- run: bundle install --path vendor/bundle
|
26
|
+
# Cache Dependencies
|
27
|
+
- type: cache-save
|
28
|
+
name: Store bundle cache
|
29
|
+
key: valkyrie-sequel-{{ checksum "Gemfile" }}
|
30
|
+
paths:
|
31
|
+
- vendor/bundle
|
32
|
+
- run: bundle exec rubocop
|
33
|
+
- run: bundle exec rspec spec
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
inherit_gem:
|
2
|
+
bixby: bixby_default.yml
|
3
|
+
AllCops:
|
4
|
+
DisplayCopNames: true
|
5
|
+
TargetRubyVersion: 2.3
|
6
|
+
Exclude:
|
7
|
+
- 'bin/*'
|
8
|
+
- 'db/schema.rb'
|
9
|
+
- 'vendor/**/*'
|
10
|
+
Naming/FileName:
|
11
|
+
Exclude:
|
12
|
+
- 'valkyrie-sequel.gemspec'
|
13
|
+
- 'Gemfile'
|
14
|
+
Metrics/BlockLength:
|
15
|
+
Exclude:
|
16
|
+
- 'spec/**/*.rb'
|
17
|
+
Metrics/ClassLength:
|
18
|
+
Exclude:
|
19
|
+
- 'lib/valkyrie/sequel/query_service.rb'
|
20
|
+
Metrics/ParameterLists:
|
21
|
+
Exclude:
|
22
|
+
- 'lib/valkyrie/sequel/metadata_adapter.rb'
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright 2018 Princeton University Library
|
2
|
+
Additional copyright may be held by others, as reflected in the commit history.
|
3
|
+
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
you may not use this file except in compliance with the License.
|
6
|
+
You may obtain a copy of the License at
|
7
|
+
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
See the License for the specific language governing permissions and
|
14
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Valkyrie::Sequel
|
2
|
+
|
3
|
+
Valkyrie adapter for postgres using [Sequel](https://github.com/jeremyevans/sequel)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'valkyrie-sequel'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install valkyrie-sequel
|
20
|
+
|
21
|
+
## Running Specs
|
22
|
+
|
23
|
+
1. Ensure Postgres is installed (`brew install postgresql` on Mac)
|
24
|
+
2. If necessary, provide the environment variables DB_USERNAME, DB_PASSWORD,
|
25
|
+
DB_HOST, DB_PORT, and DB_DATABASE to the following commands. This should not be
|
26
|
+
necessary on a local development setup, though.
|
27
|
+
3. `rake db:create`
|
28
|
+
4. `rspec spec`
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task default: :spec
|
8
|
+
|
9
|
+
namespace :db do
|
10
|
+
task :environment do
|
11
|
+
require 'valkyrie/sequel'
|
12
|
+
require_relative 'spec/support/db_connection_info'
|
13
|
+
end
|
14
|
+
desc "Create Test Database"
|
15
|
+
task create: :environment do
|
16
|
+
connection = Sequel.connect(DB_CONNECTION_INFO.merge(adapter: :postgres, database: :postgres))
|
17
|
+
begin
|
18
|
+
connection.execute "CREATE DATABASE #{DB_CONNECTION_INFO[:database]}"
|
19
|
+
puts "Database #{DB_CONNECTION_INFO[:database]} created."
|
20
|
+
rescue Sequel::DatabaseError
|
21
|
+
puts "Database already exists"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
desc "Drop Test Database"
|
25
|
+
task drop: :environment do
|
26
|
+
new_connection = Sequel.connect(DB_CONNECTION_INFO.merge(adapter: :postgres, database: :postgres))
|
27
|
+
new_connection.execute "DROP DATABASE IF EXISTS #{DB_CONNECTION_INFO[:database]}"
|
28
|
+
puts "#{DB_CONNECTION_INFO[:database]} dropped"
|
29
|
+
end
|
30
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "valkyrie/sequel"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
Sequel.migration do
|
3
|
+
up do
|
4
|
+
run 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
|
5
|
+
create_table :orm_resources do
|
6
|
+
column :id, :uuid, default: Sequel.function(:uuid_generate_v4), primary_key: true
|
7
|
+
column :metadata, :jsonb, default: '{}', index: { type: :gin }
|
8
|
+
String :internal_resource, index: true
|
9
|
+
Integer :lock_version, index: true
|
10
|
+
DateTime :created_at, index: true, default: ::Sequel::CURRENT_TIMESTAMP
|
11
|
+
DateTime :updated_at, index: true
|
12
|
+
end
|
13
|
+
run 'CREATE INDEX orm_resources_metadata_index_pathops ON orm_resources USING gin (metadata jsonb_path_ops)'
|
14
|
+
end
|
15
|
+
down do
|
16
|
+
drop_table :orm_resources
|
17
|
+
run 'DROP EXTENSION "uuid-ossp"'
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Valkyrie::Sequel
|
3
|
+
require 'valkyrie/sequel/resource_factory'
|
4
|
+
require 'valkyrie/sequel/query_service'
|
5
|
+
require 'valkyrie/sequel/persister'
|
6
|
+
class MetadataAdapter
|
7
|
+
attr_reader :connection
|
8
|
+
def initialize(connection:)
|
9
|
+
@connection = connection.tap do |conn|
|
10
|
+
conn.extension(:pg_json)
|
11
|
+
conn.extension(:pg_streaming)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def persister
|
16
|
+
@persister ||= Persister.new(adapter: self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def query_service
|
20
|
+
@query_service ||= QueryService.new(adapter: self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def resource_factory
|
24
|
+
@resource_factory ||= ResourceFactory.new(adapter: self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def id
|
28
|
+
@id ||= begin
|
29
|
+
to_hash = "sequel://#{host}:#{port}:#{database}"
|
30
|
+
Valkyrie::ID.new(Digest::MD5.hexdigest(to_hash))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def perform_migrations!
|
35
|
+
Sequel.extension :migration
|
36
|
+
Sequel::Migrator.run(connection, "#{__dir__}/../../../db/migrations")
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset_database!
|
40
|
+
Sequel.extension :migration
|
41
|
+
Sequel::Migrator.run(connection, "#{__dir__}/../../../db/migrations", target: 0)
|
42
|
+
perform_migrations!
|
43
|
+
end
|
44
|
+
|
45
|
+
def resources
|
46
|
+
connection.from(:orm_resources)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def host
|
52
|
+
connection.opts[:host]
|
53
|
+
end
|
54
|
+
|
55
|
+
def port
|
56
|
+
connection.opts[:port]
|
57
|
+
end
|
58
|
+
|
59
|
+
def database
|
60
|
+
connection.opts[:database]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Valkyrie::Sequel
|
3
|
+
class Persister
|
4
|
+
attr_reader :adapter
|
5
|
+
delegate :resource_factory, to: :adapter
|
6
|
+
delegate :resources, :connection, to: :adapter
|
7
|
+
def initialize(adapter:)
|
8
|
+
@adapter = adapter
|
9
|
+
end
|
10
|
+
|
11
|
+
def save(resource:)
|
12
|
+
object_attributes = resource_factory.from_resource(resource: resource)
|
13
|
+
output = create_or_update(resource: resource, attributes: object_attributes)
|
14
|
+
resource_factory.to_resource(object: output)
|
15
|
+
end
|
16
|
+
|
17
|
+
def save_all(resources:)
|
18
|
+
connection.transaction do
|
19
|
+
output = SaveAllPersister::Factory.new(persister: self).for(resources: resources).persist!
|
20
|
+
raise Valkyrie::Persistence::StaleObjectError, "One or more resources have been updated by another process." if output.length != resources.length
|
21
|
+
output.map do |object|
|
22
|
+
resource_factory.to_resource(object: object)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class SaveAllPersister
|
28
|
+
class Factory
|
29
|
+
delegate :adapter, to: :persister
|
30
|
+
delegate :resource_factory, to: :adapter
|
31
|
+
delegate :resources, to: :adapter
|
32
|
+
attr_reader :persister
|
33
|
+
def initialize(persister:)
|
34
|
+
@persister = persister
|
35
|
+
end
|
36
|
+
|
37
|
+
# Resources have to be handled differently based on whether or not
|
38
|
+
# optimistic locking is enabled. Splitting it into two upserts allows
|
39
|
+
# for faster save_all while still handling optimistic locking.
|
40
|
+
def for(resources:)
|
41
|
+
grouped_resources = resources.group_by(&:optimistic_locking_enabled?)
|
42
|
+
locked_resources = grouped_resources[true] || []
|
43
|
+
unlocked_resources = grouped_resources[false] || []
|
44
|
+
CompositePersister.new(
|
45
|
+
[
|
46
|
+
SaveAllPersister.new(resources: locked_resources, relation: locking_relation, resource_factory: resource_factory),
|
47
|
+
SaveAllPersister.new(resources: unlocked_resources, relation: relation, resource_factory: resource_factory)
|
48
|
+
]
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def relation
|
53
|
+
resources.returning.insert_conflict(
|
54
|
+
target: :id,
|
55
|
+
update: update_branches
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Locking relation has an update_where condition.
|
60
|
+
def locking_relation
|
61
|
+
resources.returning.insert_conflict(
|
62
|
+
target: :id,
|
63
|
+
update: update_branches,
|
64
|
+
update_where: { Sequel[:orm_resources][:lock_version] => Sequel[:excluded][:lock_version] }
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def update_branches
|
69
|
+
{
|
70
|
+
metadata: Sequel[:excluded][:metadata],
|
71
|
+
internal_resource: Sequel[:excluded][:internal_resource],
|
72
|
+
lock_version: Sequel[:excluded][:lock_version] + 1,
|
73
|
+
created_at: Sequel[:excluded][:created_at],
|
74
|
+
updated_at: Time.now.utc
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
attr_reader :resources, :relation, :resource_factory
|
79
|
+
def initialize(resources:, relation:, resource_factory:)
|
80
|
+
@resources = resources
|
81
|
+
@relation = relation
|
82
|
+
@resource_factory = resource_factory
|
83
|
+
end
|
84
|
+
|
85
|
+
def persist!
|
86
|
+
return [] if resources.empty?
|
87
|
+
Array.wrap(
|
88
|
+
relation.multi_insert(converted_resources)
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def converted_resources
|
93
|
+
@converted_resources ||= resources.map do |resource|
|
94
|
+
output = resource_factory.from_resource(resource: resource)
|
95
|
+
output[:lock_version] ||= 0
|
96
|
+
output[:created_at] ||= Time.now.utc
|
97
|
+
output[:updated_at] ||= Time.now.utc
|
98
|
+
output
|
99
|
+
end
|
100
|
+
end
|
101
|
+
class CompositePersister
|
102
|
+
attr_reader :persisters
|
103
|
+
def initialize(persisters)
|
104
|
+
@persisters = persisters
|
105
|
+
end
|
106
|
+
|
107
|
+
def persist!
|
108
|
+
persisters.flat_map(&:persist!)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
def delete(resource:)
|
113
|
+
resources.where(id: resource.id.to_s).delete
|
114
|
+
resource
|
115
|
+
end
|
116
|
+
|
117
|
+
def wipe!
|
118
|
+
resources.delete
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def create_or_update(resource:, attributes:)
|
124
|
+
attributes[:updated_at] = Time.now.utc
|
125
|
+
attributes[:created_at] ||= Time.now.utc
|
126
|
+
return create(resource: resource, attributes: attributes) unless resource.persisted? && !exists?(id: attributes[:id])
|
127
|
+
update(resource: resource, attributes: attributes)
|
128
|
+
end
|
129
|
+
|
130
|
+
def create(resource:, attributes:)
|
131
|
+
attributes[:lock_version] = 0 if resource.optimistic_locking_enabled? && resources.columns.include?(:lock_version)
|
132
|
+
Array(resources.returning.insert(attributes)).first
|
133
|
+
end
|
134
|
+
|
135
|
+
def update(resource:, attributes:)
|
136
|
+
relation = resources.where(id: attributes[:id])
|
137
|
+
if resource.optimistic_locking_enabled?
|
138
|
+
relation = relation.where(lock_version: attributes[:lock_version]) if attributes[:lock_version]
|
139
|
+
attributes[:lock_version] = (Sequel[:lock_version] + 1)
|
140
|
+
end
|
141
|
+
attributes.delete(:lock_version) if attributes[:lock_version].nil?
|
142
|
+
output = relation.returning.update(attributes)
|
143
|
+
raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." if output.blank? && resource.optimistic_locking_enabled?
|
144
|
+
Array(output).first
|
145
|
+
end
|
146
|
+
|
147
|
+
def exists?(id:)
|
148
|
+
resources.select(1).first(id: id).nil?
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Valkyrie::Sequel
|
3
|
+
class QueryService
|
4
|
+
ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
|
5
|
+
attr_reader :adapter
|
6
|
+
delegate :resources, :resource_factory, :connection, to: :adapter
|
7
|
+
def initialize(adapter:)
|
8
|
+
@adapter = adapter
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_all
|
12
|
+
resources.use_cursor.lazy.map do |attributes|
|
13
|
+
resource_factory.to_resource(object: attributes)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_by(id:)
|
18
|
+
id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
|
19
|
+
validate_id(id)
|
20
|
+
raise Valkyrie::Persistence::ObjectNotFoundError unless ACCEPTABLE_UUID.match?(id.to_s)
|
21
|
+
attributes = resources.first(id: id.to_s)
|
22
|
+
raise Valkyrie::Persistence::ObjectNotFoundError unless attributes
|
23
|
+
resource_factory.to_resource(object: attributes)
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_all_of_model(model:)
|
27
|
+
resources.where(internal_resource: model.to_s).map do |attributes|
|
28
|
+
resource_factory.to_resource(object: attributes)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_many_by_ids(ids:)
|
33
|
+
ids = ids.map do |id|
|
34
|
+
id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
|
35
|
+
validate_id(id)
|
36
|
+
id.to_s
|
37
|
+
end
|
38
|
+
ids = ids.select do |id|
|
39
|
+
ACCEPTABLE_UUID.match?(id)
|
40
|
+
end
|
41
|
+
|
42
|
+
resources.where(id: ids).map do |attributes|
|
43
|
+
resource_factory.to_resource(object: attributes)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_references_by(resource:, property:)
|
48
|
+
return [] if resource.id.blank? || resource[property].blank?
|
49
|
+
# only return ordered if needed to avoid performance penalties
|
50
|
+
if ordered_property?(resource: resource, property: property)
|
51
|
+
run_query(find_ordered_references_query, property.to_s, resource.id.to_s)
|
52
|
+
else
|
53
|
+
run_query(find_references_query, property.to_s, resource.id.to_s)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_inverse_references_by(resource: nil, id: nil, property:)
|
58
|
+
raise ArgumentError, "Provide resource or id" unless resource || id
|
59
|
+
ensure_persisted(resource) if resource
|
60
|
+
id ||= resource.id
|
61
|
+
internal_array = { property => [id: id.to_s] }
|
62
|
+
run_query(find_inverse_references_query, internal_array.to_json)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Find and a record using a Valkyrie ID for an alternate ID, and construct
|
66
|
+
# a Valkyrie Resource
|
67
|
+
# @param [Valkyrie::ID] alternate_identifier
|
68
|
+
# @return [Valkyrie::Resource]
|
69
|
+
def find_by_alternate_identifier(alternate_identifier:)
|
70
|
+
alternate_identifier = Valkyrie::ID.new(alternate_identifier.to_s) if alternate_identifier.is_a?(String)
|
71
|
+
validate_id(alternate_identifier)
|
72
|
+
internal_array = { alternate_ids: [{ id: alternate_identifier.to_s }] }
|
73
|
+
run_query(find_inverse_references_query, internal_array.to_json).first || raise(Valkyrie::Persistence::ObjectNotFoundError)
|
74
|
+
end
|
75
|
+
|
76
|
+
def find_members(resource:, model: nil)
|
77
|
+
return [] if resource.id.blank?
|
78
|
+
if model
|
79
|
+
run_query(find_members_with_type_query, resource.id.to_s, model.to_s)
|
80
|
+
else
|
81
|
+
run_query(find_members_query, resource.id.to_s)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_parents(resource:)
|
86
|
+
find_inverse_references_by(resource: resource, property: :member_ids)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Constructs a Valkyrie::Persistence::CustomQueryContainer using this query service
|
90
|
+
# @return [Valkyrie::Persistence::CustomQueryContainer]
|
91
|
+
def custom_queries
|
92
|
+
@custom_queries ||= ::Valkyrie::Persistence::CustomQueryContainer.new(query_service: self)
|
93
|
+
end
|
94
|
+
|
95
|
+
def run_query(query, *args)
|
96
|
+
connection[query, *args].map do |result|
|
97
|
+
resource_factory.to_resource(object: result)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Generate the SQL query for retrieving member resources in PostgreSQL using a
|
104
|
+
# resource ID as an argument.
|
105
|
+
# @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
|
106
|
+
# @note this uses a CROSS JOIN for all combinations of member IDs with the
|
107
|
+
# IDs of their parents
|
108
|
+
# @see https://www.postgresql.org/docs/current/static/queries-table-expressions.html#QUERIES-FROM
|
109
|
+
# This also uses JSON functions in order to retrieve JSON property values
|
110
|
+
# @see https://www.postgresql.org/docs/current/static/functions-json.html
|
111
|
+
# @return [String]
|
112
|
+
def find_members_query
|
113
|
+
<<-SQL
|
114
|
+
SELECT member.* FROM orm_resources a,
|
115
|
+
jsonb_array_elements(a.metadata->'member_ids') WITH ORDINALITY AS b(member, member_pos)
|
116
|
+
JOIN orm_resources member ON (b.member->>'id')::#{id_type} = member.id WHERE a.id = ?
|
117
|
+
ORDER BY b.member_pos
|
118
|
+
SQL
|
119
|
+
end
|
120
|
+
|
121
|
+
# Generate the SQL query for retrieving member resources in PostgreSQL using a
|
122
|
+
# resource ID and resource type as arguments.
|
123
|
+
# @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
|
124
|
+
# @note this uses a CROSS JOIN for all combinations of member IDs with the
|
125
|
+
# IDs of their parents
|
126
|
+
# @see https://www.postgresql.org/docs/current/static/queries-table-expressions.html#QUERIES-FROM
|
127
|
+
# This also uses JSON functions in order to retrieve JSON property values
|
128
|
+
# @see https://www.postgresql.org/docs/current/static/functions-json.html
|
129
|
+
# @return [String]
|
130
|
+
def find_members_with_type_query
|
131
|
+
<<-SQL
|
132
|
+
SELECT member.* FROM orm_resources a,
|
133
|
+
jsonb_array_elements(a.metadata->'member_ids') WITH ORDINALITY AS b(member, member_pos)
|
134
|
+
JOIN orm_resources member ON (b.member->>'id')::#{id_type} = member.id WHERE a.id = ?
|
135
|
+
AND member.internal_resource = ?
|
136
|
+
ORDER BY b.member_pos
|
137
|
+
SQL
|
138
|
+
end
|
139
|
+
|
140
|
+
# Generate the SQL query for retrieving member resources in PostgreSQL using a
|
141
|
+
# JSON object literal as an argument (e. g. { "alternate_ids": [{"id": "d6e88f80-41b3-4dbf-a2a0-cd79e20f6d10"}] }).
|
142
|
+
# @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
|
143
|
+
# This uses JSON functions in order to retrieve JSON property values
|
144
|
+
# @see https://www.postgresql.org/docs/current/static/functions-json.html
|
145
|
+
# @return [String]
|
146
|
+
def find_inverse_references_query
|
147
|
+
<<-SQL
|
148
|
+
SELECT * FROM orm_resources WHERE
|
149
|
+
metadata @> ?
|
150
|
+
SQL
|
151
|
+
end
|
152
|
+
|
153
|
+
# Generate the SQL query for retrieving member resources in PostgreSQL using a
|
154
|
+
# JSON object literal and resource ID as arguments.
|
155
|
+
# @see https://guides.rubyonrails.org/active_record_querying.html#array-conditions
|
156
|
+
# @note this uses a CROSS JOIN for all combinations of member IDs with the
|
157
|
+
# IDs of their parents
|
158
|
+
# @see https://www.postgresql.org/docs/current/static/queries-table-expressions.html#QUERIES-FROM
|
159
|
+
# This also uses JSON functions in order to retrieve JSON property values
|
160
|
+
# @see https://www.postgresql.org/docs/current/static/functions-json.html
|
161
|
+
# @return [String]
|
162
|
+
def find_references_query
|
163
|
+
<<-SQL
|
164
|
+
SELECT DISTINCT member.* FROM orm_resources a,
|
165
|
+
jsonb_array_elements(a.metadata->?) AS b(member)
|
166
|
+
JOIN orm_resources member ON (b.member->>'id')::#{id_type} = member.id WHERE a.id = ?
|
167
|
+
SQL
|
168
|
+
end
|
169
|
+
|
170
|
+
def find_ordered_references_query
|
171
|
+
<<-SQL
|
172
|
+
SELECT member.* FROM orm_resources a,
|
173
|
+
jsonb_array_elements(a.metadata->?) WITH ORDINALITY AS b(member, member_pos)
|
174
|
+
JOIN orm_resources member ON (b.member->>'id')::#{id_type} = member.id WHERE a.id = ?
|
175
|
+
ORDER BY b.member_pos
|
176
|
+
SQL
|
177
|
+
end
|
178
|
+
|
179
|
+
# Accesses the data type in PostgreSQL used for the primary key
|
180
|
+
# (For example, a UUID)
|
181
|
+
# @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaCache.html#method-i-columns_hash
|
182
|
+
# @return [Symbol]
|
183
|
+
def id_type
|
184
|
+
@id_type ||= :uuid
|
185
|
+
end
|
186
|
+
|
187
|
+
# Determines whether or not an Object is a Valkyrie ID
|
188
|
+
# @param [Object] id
|
189
|
+
# @raise [ArgumentError]
|
190
|
+
def validate_id(id)
|
191
|
+
raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
|
192
|
+
end
|
193
|
+
|
194
|
+
# Determines whether or not a resource has been persisted
|
195
|
+
# @param [Object] resource
|
196
|
+
# @raise [ArgumentError]
|
197
|
+
def ensure_persisted(resource)
|
198
|
+
raise ArgumentError, 'resource is not saved' unless resource.persisted?
|
199
|
+
end
|
200
|
+
|
201
|
+
def ordered_property?(resource:, property:)
|
202
|
+
resource.class.schema[property].meta.try(:[], :ordered)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Valkyrie::Sequel
|
3
|
+
class ResourceFactory::ORMConverter
|
4
|
+
attr_reader :object, :resource_factory
|
5
|
+
def initialize(object, resource_factory:)
|
6
|
+
@object = object
|
7
|
+
@resource_factory = resource_factory
|
8
|
+
end
|
9
|
+
|
10
|
+
def convert!
|
11
|
+
@resource ||= resource
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Construct a new Valkyrie Resource using the attributes retrieved from the database
|
17
|
+
# @return [Valkyrie::Resource]
|
18
|
+
def resource
|
19
|
+
resource_klass.new(
|
20
|
+
attributes.merge(
|
21
|
+
new_record: false,
|
22
|
+
Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK => lock_token
|
23
|
+
)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Construct the optimistic lock token using the adapter and lock version for the Resource
|
28
|
+
# @return [Valkyrie::Persistence::OptimisticLockToken]
|
29
|
+
def lock_token
|
30
|
+
@lock_token ||=
|
31
|
+
Valkyrie::Persistence::OptimisticLockToken.new(
|
32
|
+
adapter_id: resource_factory.adapter_id,
|
33
|
+
token: object[:lock_version]
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Retrieve the Class used to construct the Valkyrie Resource
|
38
|
+
# @return [Class]
|
39
|
+
def resource_klass
|
40
|
+
internal_resource.constantize
|
41
|
+
end
|
42
|
+
|
43
|
+
# Access the String for the Valkyrie Resource type within the attributes
|
44
|
+
# @return [String]
|
45
|
+
def internal_resource
|
46
|
+
attributes[:internal_resource]
|
47
|
+
end
|
48
|
+
|
49
|
+
def attributes
|
50
|
+
@attributes ||= object.except(:metadata).merge(rdf_metadata).symbolize_keys
|
51
|
+
end
|
52
|
+
|
53
|
+
# Generate a Hash derived from Valkyrie Resource metadata encoded in the RDF
|
54
|
+
# @return [Hash]
|
55
|
+
def rdf_metadata
|
56
|
+
@rdf_metadata ||= Valkyrie::Persistence::Postgres::ORMConverter::RDFMetadata.new(object[:metadata]).result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Valkyrie::Sequel
|
3
|
+
class ResourceFactory::ResourceConverter
|
4
|
+
attr_reader :resource, :resource_factory
|
5
|
+
delegate :orm_class, :adapter, to: :resource_factory
|
6
|
+
delegate :resources, to: :adapter
|
7
|
+
def initialize(resource, resource_factory:)
|
8
|
+
@resource = resource
|
9
|
+
@resource_factory = resource_factory
|
10
|
+
end
|
11
|
+
|
12
|
+
def convert!
|
13
|
+
output = database_hash
|
14
|
+
output[:id] = resource.id.to_s if resource.id
|
15
|
+
output.delete(:id) unless !output[:id] || QueryService::ACCEPTABLE_UUID.match?(output[:id].to_s)
|
16
|
+
process_lock_token(output)
|
17
|
+
output
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Retrieves the optimistic lock token from the Valkyrie attribute value and
|
23
|
+
# sets it to the lock_version on ORM resource
|
24
|
+
# @see https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
|
25
|
+
# @param [ORM::Resource] orm_object
|
26
|
+
def process_lock_token(orm_object)
|
27
|
+
return unless resource.respond_to?(Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK)
|
28
|
+
postgres_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find do |token|
|
29
|
+
token.adapter_id == resource_factory.adapter_id
|
30
|
+
end
|
31
|
+
return unless postgres_token
|
32
|
+
orm_object[:lock_version] = postgres_token.token
|
33
|
+
end
|
34
|
+
|
35
|
+
def database_hash
|
36
|
+
resource_hash.select do |k, _v|
|
37
|
+
primary_terms.include?(k)
|
38
|
+
end.compact.merge(
|
39
|
+
metadata: ::Sequel.pg_json(metadata_hash)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def resource_hash
|
44
|
+
@resource_hash ||= resource.to_h
|
45
|
+
end
|
46
|
+
|
47
|
+
# Convert attributes to all be arrays to better enable querying and
|
48
|
+
# "changing of minds" later on.
|
49
|
+
# @return [Hash]
|
50
|
+
def metadata_hash
|
51
|
+
Hash[
|
52
|
+
selected_resource_attributes.compact.map do |k, v|
|
53
|
+
[k, Array.wrap(v)]
|
54
|
+
end
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
def selected_resource_attributes
|
59
|
+
resource_hash.select do |k, _v|
|
60
|
+
!primary_terms.include?(k) && !blacklist_terms.include?(k)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def primary_terms
|
65
|
+
[
|
66
|
+
:id,
|
67
|
+
:created_at,
|
68
|
+
:updated_at,
|
69
|
+
:internal_resource
|
70
|
+
]
|
71
|
+
end
|
72
|
+
|
73
|
+
def blacklist_terms
|
74
|
+
[
|
75
|
+
:new_record
|
76
|
+
]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Valkyrie::Sequel
|
3
|
+
class ResourceFactory
|
4
|
+
require 'valkyrie/sequel/resource_factory/resource_converter'
|
5
|
+
require 'valkyrie/sequel/resource_factory/orm_converter'
|
6
|
+
attr_reader :adapter
|
7
|
+
delegate :id, to: :adapter, prefix: true
|
8
|
+
def initialize(adapter:)
|
9
|
+
@adapter = adapter
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_resource(object:)
|
13
|
+
ORMConverter.new(object, resource_factory: self).convert!
|
14
|
+
end
|
15
|
+
|
16
|
+
def from_resource(resource:)
|
17
|
+
ResourceConverter.new(resource, resource_factory: self).convert!
|
18
|
+
end
|
19
|
+
|
20
|
+
def orm_class
|
21
|
+
adapter.resources
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "valkyrie/sequel/version"
|
3
|
+
require 'valkyrie'
|
4
|
+
require 'sequel'
|
5
|
+
require 'sequel/adapters/postgresql'
|
6
|
+
require 'sequel_pg'
|
7
|
+
|
8
|
+
module Valkyrie
|
9
|
+
module Sequel
|
10
|
+
::Sequel.extension(:pg_json)
|
11
|
+
::Sequel.extension(:pg_json_ops)
|
12
|
+
::Sequel.default_timezone = :utc
|
13
|
+
::Sequel.extension(:oj_parser)
|
14
|
+
require 'valkyrie/sequel/metadata_adapter'
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
# frozen_string_literal: true
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "valkyrie/sequel/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "valkyrie-sequel"
|
9
|
+
spec.version = Valkyrie::Sequel::VERSION
|
10
|
+
spec.authors = ["Trey Pendragon"]
|
11
|
+
spec.email = ["tpendragon@princeton.edu"]
|
12
|
+
|
13
|
+
spec.summary = 'Valkyrie::MetadataAdapter for Postgres using Sequel.'
|
14
|
+
spec.description = 'Valkyrie::MetadataAdapter for Postgres using Sequel.'
|
15
|
+
spec.homepage = "https://github.com/samvera-labs/valkyrie-sequel"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency "sequel"
|
25
|
+
spec.add_dependency "sequel_pg"
|
26
|
+
spec.add_dependency "valkyrie", "~> 1.5.0.RC1"
|
27
|
+
spec.add_dependency "oj"
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
+
spec.add_development_dependency "bixby"
|
32
|
+
spec.add_development_dependency "pry-byebug"
|
33
|
+
spec.add_development_dependency "database_cleaner"
|
34
|
+
spec.add_development_dependency "coveralls"
|
35
|
+
spec.add_development_dependency "simplecov"
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: valkyrie-sequel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Trey Pendragon
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sequel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sequel_pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: valkyrie
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.5.0.RC1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.5.0.RC1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: oj
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.16'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.16'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: bixby
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry-byebug
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: database_cleaner
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: coveralls
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: simplecov
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
description: Valkyrie::MetadataAdapter for Postgres using Sequel.
|
182
|
+
email:
|
183
|
+
- tpendragon@princeton.edu
|
184
|
+
executables: []
|
185
|
+
extensions: []
|
186
|
+
extra_rdoc_files: []
|
187
|
+
files:
|
188
|
+
- ".circleci/config.yml"
|
189
|
+
- ".gitignore"
|
190
|
+
- ".rspec"
|
191
|
+
- ".rubocop.yml"
|
192
|
+
- ".travis.yml"
|
193
|
+
- Gemfile
|
194
|
+
- LICENSE
|
195
|
+
- README.md
|
196
|
+
- Rakefile
|
197
|
+
- bin/console
|
198
|
+
- bin/setup
|
199
|
+
- db/migrations/.gitkeep
|
200
|
+
- db/migrations/001_create_orm_resources.rb
|
201
|
+
- lib/sequel/extensions/oj_parser.rb
|
202
|
+
- lib/valkyrie/sequel.rb
|
203
|
+
- lib/valkyrie/sequel/metadata_adapter.rb
|
204
|
+
- lib/valkyrie/sequel/persister.rb
|
205
|
+
- lib/valkyrie/sequel/query_service.rb
|
206
|
+
- lib/valkyrie/sequel/resource_factory.rb
|
207
|
+
- lib/valkyrie/sequel/resource_factory/orm_converter.rb
|
208
|
+
- lib/valkyrie/sequel/resource_factory/resource_converter.rb
|
209
|
+
- lib/valkyrie/sequel/version.rb
|
210
|
+
- valkyrie-sequel.gemspec
|
211
|
+
homepage: https://github.com/samvera-labs/valkyrie-sequel
|
212
|
+
licenses: []
|
213
|
+
metadata: {}
|
214
|
+
post_install_message:
|
215
|
+
rdoc_options: []
|
216
|
+
require_paths:
|
217
|
+
- lib
|
218
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
224
|
+
requirements:
|
225
|
+
- - ">="
|
226
|
+
- !ruby/object:Gem::Version
|
227
|
+
version: '0'
|
228
|
+
requirements: []
|
229
|
+
rubyforge_project:
|
230
|
+
rubygems_version: 2.6.14
|
231
|
+
signing_key:
|
232
|
+
specification_version: 4
|
233
|
+
summary: Valkyrie::MetadataAdapter for Postgres using Sequel.
|
234
|
+
test_files: []
|