Skip to content

Commit

Permalink
Merge branch 'master' into update_dry_struct
Browse files Browse the repository at this point in the history
  • Loading branch information
escowles authored Aug 9, 2018
2 parents a0f1bfd + 9a3431c commit 9eecf21
Show file tree
Hide file tree
Showing 72 changed files with 1,139 additions and 191 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ RSpec/MultipleExpectations:
Enabled: false
Rails/TimeZone:
Enabled: false
Style/PredicateName:
Naming/PredicateName:
Exclude:
- "lib/valkyrie/resource.rb"
- "lib/valkyrie/persistence/solr/queries/default_paginator.rb"
Expand Down
5 changes: 5 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
Metrics/ClassLength:
Exclude:
- 'lib/valkyrie/persistence/fedora/persister.rb'
- 'lib/valkyrie/persistence/postgres/query_service.rb'
- 'lib/valkyrie/resource.rb'

Metrics/MethodLength:
Exclude:
- 'lib/valkyrie/persistence/fedora/persister.rb'
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
# v1.2.0.RC1 2018-08-09

## Changes since last release

* Support for single values.
[Documentation](https://github.com/samvera-labs/valkyrie/wiki/Using-Types#singular-values)
* Optimistic Locking.
[Documentation](https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking)
* Remove reliance on ActiveFedora for Fedora Storage Adapter.
* Only load adapters if referenced.
* Postgres Adapter uses transactions for `save_all`
* Resources now include `id` attribute by default.

## Special Thanks

This release was made possible by a community sprint undertaken between Penn
State University Libraries & Princeton University Library. Thanks to the
following participants who made it happen:

* [awead](https://github.com/awead)
* [cam156](https://github.com/cam156)
* [DanCoughlin](https://github.com/DanCoughlin)
* [escowles](https://github.com/escowles)
* [hackmastera](https://github.com/hackmastera)
* [jrgriffiniii](https://github.com/jrgriffiniii)
* [mtribone](https://github.com/mtribone)
* [tpendragon](https://github.com/tpendragon)

# v1.1.2 2018-06-08

## Changes since last release
Expand Down
50 changes: 43 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@ Valkyrie is a gem for enabling multiple backends for storage of files and metada
[![CircleCI](https://circleci.com/gh/samvera-labs/valkyrie.svg?style=svg)](https://circleci.com/gh/samvera-labs/valkyrie)
[![Coverage Status](https://coveralls.io/repos/github/samvera-labs/valkyrie/badge.svg?branch=master)](https://coveralls.io/github/samvera-labs/valkyrie?branch=master)
[![Stories in Ready](https://badge.waffle.io/samvera-labs/valkyrie.png?label=ready&title=Ready)](https://waffle.io/samvera-labs/valkyrie)
[![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/samvera-labs/valkyrie)

## Primary Contacts

### Product Owner

[Carolyn Cole](https://github.com/cam156)

### Technical Lead

[Trey Pendragon](https://github.com/tpendragon)

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'valkyrie', github: 'samvera-labs/valkyrie'
```
gem 'valkyrie'
```

And then execute:

$ bundle


## Configuration

Valkyrie is configured in two places: an initializer that registers the persistence options and a YAML
Expand All @@ -30,6 +39,7 @@ configuration file that sets which options are used by default in which environm
Here is a sample initializer that registers a couple adapters and storage adapters, in each case linking an
instance with a short name that can be used to refer to it in your application:


```
# frozen_string_literal: true
require 'valkyrie'
Expand All @@ -50,7 +60,7 @@ Rails.application.config.to_prepare do
)
Valkyrie::StorageAdapter.register(
Valkyrie::Storage::Fedora.new(connection: ActiveFedora.fedora.connection),
Valkyrie::Storage::Fedora.new(connection: Ldp::Client.new("http://localhost:8988/rest")),
:fedora
)
Expand All @@ -69,7 +79,7 @@ The initializer registers two `Valkyrie::MetadataAdapter` instances for storing

Other adapter options include `Valkyrie::Persistence::BufferedPersister` for buffering in memory before bulk
updating another persister, `Valkyrie::Persistence::CompositePersister` for storing in more than one adapter
at once, and `Valkyrie::Persistence::Solr` for storing in Solr.
at once, `Valkyrie::Persistence::Solr` for storing in Solr, and `Valkyrie::Persistence::Fedora` for storing in Fedora.

The initializer also registers three `Valkyrie::StorageAdapter` instances for storing files:
* `:disk` which stores files on disk
Expand Down Expand Up @@ -101,9 +111,23 @@ For each environment, you must set two values:

The values are the short names used in your initializer.

Further details can be found on the [the Wiki](https://github.com/samvera-labs/valkyrie/wiki/Persistence).

## Usage

### The Public API

Valkyrie's public API is defined by the shared specs that are used to test each of its core classes.
This include change sets, resources, persisters, adapters, and queries. When creating your own kinds of
these kinds of classes, you should use these shared specs to test your classes for conformance to
Valkyrie's API.

When breaking changes are introduced, necessitating a major version change, the shared specs will reflect
this. Likewise, non-breaking changes to Valkyrie can be defined as code changes that do not cause any
errors with the current shared specs.

Using the shared specs in your own models is described in more [detail](https://github.com/samvera-labs/valkyrie/wiki/Shared-Specs).

### Define a Custom Work

Define a custom work class:
Expand All @@ -112,12 +136,13 @@ Define a custom work class:
# frozen_string_literal: true
class MyModel < Valkyrie::Resource
include Valkyrie::Resource::AccessControls
attribute :id, Valkyrie::Types::ID.optional # Optional to allow auto-generation of IDs
attribute :title, Valkyrie::Types::Set # Sets are unordered
attribute :authors, Valkyrie::Types::Array # Arrays are ordered
end
```

Defining resource attributes is explained in greater detail within the [Wiki](https://github.com/samvera-labs/valkyrie/wiki/Using-Types).

#### Work Types Generator

To create a custom Valkyrie model in your application, you can use the Rails generator. For example, to
Expand Down Expand Up @@ -153,6 +178,10 @@ objects = adapter.query_service.find_all
Valkyrie.config.metadata_adapter.query_service.find_all_of_model(model: MyModel)
```

The usage of `ChangeSets` in writing data are further documented [here](https://github.com/samvera-labs/valkyrie/wiki/ChangeSets-and-Dirty-Tracking).

### Concurrency Support (Optimistic Locking)
By default, it is assumed that a Valkyrie repository implementation shall use a solution supporting concurrent updates for resources (multiple resources can be updated simultaneously using a Gem such as [Sidekiq](https://github.com/mperham/sidekiq)). In order to handle the possibility of multiple updates applied to the same resource corrupting data, Valkyrie supports optimistic locking. For further details, please reference the [overview of optimistic locking for Valkyrie resources](https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking).

## Installing a Development environment

Expand Down Expand Up @@ -183,7 +212,7 @@ Valkyrie.config.metadata_adapter.query_service.find_all_of_model(model: MyModel)

1. `docker-machine create default`
1. `docker-machine start default`
1. `eval "$(docker-machine env)"
1. `eval "$(docker-machine env)"`

#### Starting the development mode dependencies
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)
Expand All @@ -204,6 +233,13 @@ Valkyrie.config.metadata_adapter.query_service.find_all_of_model(model: MyModel)

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.

## Get Help

If you have any questions regarding Valkyrie you can send a message to [the
Samvera community tech list](mailto:samvera-tech@googlegroups.com) or the `#valkyrie`
channel in the [Samvera community Slack
team](https://wiki.duraspace.org/pages/viewpage.action?pageId=87460391#Getintouch!-Slack).

## License

Valkyrie is available under [the Apache 2.0 license](../LICENSE).
Expand Down
10 changes: 0 additions & 10 deletions config/fedora.yml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true
class AddOptimisticLockingToOrmResources < ActiveRecord::Migration[5.1]
def change
add_column :orm_resources, :lock_version, :integer
end
end
1 change: 0 additions & 1 deletion lib/generators/valkyrie/templates/resource.rb.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# Generated with `rails generate valkyrie:model <%= class_name %>`
class <%= class_name %> < Valkyrie::Resource
include Valkyrie::Resource::AccessControls
attribute :id, Valkyrie::Types::ID.optional
<%- attributes.each do |att| -%>
attribute :<%= att.name %>, Valkyrie::Types::<%= (att.type == :array) ? 'Array' : 'Set' %>
<%- end -%>
Expand Down
2 changes: 0 additions & 2 deletions lib/valkyrie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
require 'valkyrie/rdf_patches'
require 'json/ld'
require 'logger'
require 'active_triples'
require 'rdf/vocab'
require 'rails'
require 'active_fedora'

module Valkyrie
require 'valkyrie/id'
Expand Down
4 changes: 4 additions & 0 deletions lib/valkyrie/change_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,23 @@ class ChangeSet < Reform::Form
property :append_id, virtual: true

# Set ID of record this one should be appended to.
# We use append_id to add a member/child onto an existing list of members.
# @param append_id [Valkyrie::ID]
def append_id=(append_id)
super(Valkyrie::ID.new(append_id))
end

# Returns whether or not a given field has multiple values.
# Multiple values are useful for fields like creator, author, title, etc.
# where there may be more than one value for a field that is stored and returned in the UI
# @param field_name [Symbol]
# @return [Boolean]
def multiple?(field_name)
field(field_name)[:multiple] != false
end

# Returns whether or not a given field is required.
# Useful for distinguishing required fields in a form and for validation
# @param field_name [Symbol]
# @return [Boolean]
def required?(field_name)
Expand Down
5 changes: 5 additions & 0 deletions lib/valkyrie/id.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module Valkyrie
# A simple ID class to keep IDs distinguished from strings
# In order for an object to be queryable via joins, it needs
# to be added as a reference via a Valkyrie::ID rather than just a string ID.
class ID
attr_reader :id
delegate :empty?, to: :id
Expand All @@ -19,8 +21,11 @@ def eql?(other)
end
alias == eql?

# @deprecated Please use {.uri_for} instead
def to_uri
return RDF::Literal.new(id.to_s, datatype: RDF::URI("http://example.com/valkyrie_id")) if id.to_s.include?("://")
warn "[DEPRECATION] `to_uri` is deprecated and will be removed in the next major release. " \
"Called from #{Gem.location_of_caller.join(':')}"
::RDF::URI(ActiveFedora::Base.id_to_uri(id))
end

Expand Down
1 change: 1 addition & 0 deletions lib/valkyrie/metadata_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class MetadataAdapter
self.adapters = {}
class << self
# Register an adapter by a short name.
# Registering an adapter by a short name makes the adapter easier to find and reference.
# @param adapter [#persister,#query_service] Adapter to register.
# @param short_name [Symbol] Name to register it under.
def register(adapter, short_name)
Expand Down
13 changes: 10 additions & 3 deletions lib/valkyrie/persistence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,24 @@ module Valkyrie
# @see lib/valkyrie/specs/shared_specs/persister.rb
#
module Persistence
require 'valkyrie/persistence/optimistic_lock_token'
require 'valkyrie/persistence/custom_query_container'
require 'valkyrie/persistence/memory'
require 'valkyrie/persistence/postgres'
require 'valkyrie/persistence/solr'
require 'valkyrie/persistence/fedora'
require 'valkyrie/persistence/composite_persister'
require 'valkyrie/persistence/delete_tracking_buffer'
require 'valkyrie/persistence/buffered_persister'
autoload :Postgres, 'valkyrie/persistence/postgres'
autoload :Solr, 'valkyrie/persistence/solr'
autoload :Fedora, 'valkyrie/persistence/fedora'
class ObjectNotFoundError < StandardError
end
class UnsupportedDatatype < StandardError
end
class StaleObjectError < StandardError
end

module Attributes
OPTIMISTIC_LOCK = :optimistic_lock_token
end
end
end
13 changes: 12 additions & 1 deletion lib/valkyrie/persistence/composite_persister.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,25 @@ def initialize(*persisters)

# (see Valkyrie::Persistence::Memory::Persister#save)
def save(resource:)
persisters.inject(resource) { |m, persister| persister.save(resource: m) }
# Assume the first persister is the canonical data store; that's the optlock we want
first, *rest = *persisters
cached_resource = first.save(resource: resource)
# Don't pass opt lock tokens to other persisters
internal_resource = cached_resource.dup
internal_resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", []) if internal_resource.optimistic_locking_enabled?
rest.inject(internal_resource) { |m, persister| persister.save(resource: m) }
# return the one with the desired opt lock token
cached_resource
end

# (see Valkyrie::Persistence::Memory::Persister#save_all)
def save_all(resources:)
resources.map do |resource|
save(resource: resource)
end
rescue Valkyrie::Persistence::StaleObjectError
# clear out any IDs returned to reduce potential confusion
raise Valkyrie::Persistence::StaleObjectError, "One or more resources have been updated by another process."
end

# (see Valkyrie::Persistence::Memory::Persister#delete)
Expand Down
2 changes: 2 additions & 0 deletions lib/valkyrie/persistence/fedora.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Valkyrie::Persistence
# Implements the DataMapper Pattern to store metadata into Fedora
module Fedora
require 'active_triples'
require 'active_fedora'
require 'valkyrie/persistence/fedora/permissive_schema'
require 'valkyrie/persistence/fedora/metadata_adapter'
require 'valkyrie/persistence/fedora/persister'
Expand Down
6 changes: 3 additions & 3 deletions lib/valkyrie/persistence/fedora/list_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def initialize(node_cache, rdf_subject, adapter, graph = RDF::Repository.new)
end

# Returns the next proxy or a tail sentinel.
# @return [ActiveFedora::Orders::ListNode]
# @return [RDF::URI]
def next
@next ||=
if next_uri
Expand All @@ -31,13 +31,13 @@ def next
end

# Returns the previous proxy or a head sentinel.
# @return [ActiveFedora::Orders::ListNode]
# @return [RDF::URI]
def prev
@prev ||= node_cache.fetch(prev_uri) if prev_uri
end

# Graph representation of node.
# @return [ActiveFedora::Orders::ListNode::Resource]
# @return [Valkyrie::Persistence::Fedora::ListNode::Resource]
def to_graph
return RDF::Graph.new if target_id.blank?
g = Resource.new(rdf_subject)
Expand Down
4 changes: 4 additions & 0 deletions lib/valkyrie/persistence/fedora/metadata_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def persister
Valkyrie::Persistence::Fedora::Persister.new(adapter: self)
end

def id
@id ||= Valkyrie::ID.new(Digest::MD5.hexdigest(connection_prefix))
end

def resource_factory
Valkyrie::Persistence::Fedora::Persister::ResourceFactory.new(adapter: self)
end
Expand Down
5 changes: 5 additions & 0 deletions lib/valkyrie/persistence/fedora/permissive_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ def self.valkyrie_time
uri_for(:valkyrie_time)
end

# @return [RDF::URI]
def self.optimistic_lock_token
uri_for(:optimistic_lock_token)
end

# Cast the property to a URI in the namespace
# @param property [Symbol]
# @return [RDF::URI]
Expand Down
Loading

0 comments on commit 9eecf21

Please sign in to comment.