Skip to content

Commit

Permalink
Merge pull request #385 from pulibrary/issues-364-jrgriffiniii-epheme…
Browse files Browse the repository at this point in the history
…ra-project-manifest

Ensures that Ephemera Projects have IIIF Manifests
  • Loading branch information
Trey Pendragon committed Oct 18, 2017
2 parents 58f846d + db322e9 commit 4eeb873
Show file tree
Hide file tree
Showing 35 changed files with 566 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def initialize(change_set_persister:, change_set:, post_save_resource: nil)

def run
messenger.record_updated(post_save_resource)
# For cases where the resource is a FileSet, propagate for the parent resource
messenger.record_updated(post_save_resource.decorate.parent) if post_save_resource.is_a? FileSet
end

delegate :messenger, to: :change_set_persister
Expand Down
30 changes: 28 additions & 2 deletions app/change_sets/ephemera_project_change_set.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
# frozen_string_literal: true
class EphemeraProjectChangeSet < Valkyrie::ChangeSet
validates :title, presence: true
validates :title, :slug, presence: true
property :title, multiple: false
property :member_ids, multiple: true, required: false, type: Types::Strict::Array.member(Valkyrie::Types::ID)
property :slug, multiple: false, required: true
validate :slug_unique?
validate :slug_valid?

def primary_terms
[:title]
[:title, :slug]
end

def slug_valid?
return if Slug.new(Array.wrap(slug).first).valid?
errors.add(:slug, 'contains invalid characters, please only use alphanumerics, dashes, and underscores')
end

def slug_unique?
return unless slug_exists?
errors.add(:slug, 'is already in use by another project')
end

private

def metadata_adapter
Valkyrie.config.metadata_adapter
end
delegate :query_service, to: :metadata_adapter

def slug_exists?
slug_value = Array.wrap(slug).first
results = query_service.custom_queries.find_by_string_property(property: :slug, value: slug_value).to_a
!results.empty?
end
end
11 changes: 10 additions & 1 deletion app/controllers/ephemera_projects_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class EphemeraProjectsController < ApplicationController
class EphemeraProjectsController < BaseResourceController
include Valhalla::ResourceController
include TokenAuth
self.change_set_class = DynamicChangeSet
Expand All @@ -14,6 +14,15 @@ def index
render 'index'
end

def manifest
@resource = find_resource(params[:id])
respond_to do |f|
f.json do
render json: ManifestBuilder.new(@resource).build
end
end
end

private

def load_ephemera_projects
Expand Down
2 changes: 1 addition & 1 deletion app/decorators/collection_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def manageable_files?
end

def members
@members ||= query_service.find_inverse_references_by(resource: self, property: :member_of_collection_ids).to_a
@members ||= query_service.find_inverse_references_by(resource: model, property: :member_of_collection_ids).to_a
end

# Nested collections are not currently supported
Expand Down
10 changes: 9 additions & 1 deletion app/decorators/ephemera_box_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,21 @@ def title
end

def members
@members ||= find_members(resource: model)
@members ||= query_service.find_members(resource: model).to_a
end

def folders
@folders ||= members.select { |r| r.is_a?(EphemeraFolder) }.map(&:decorate).to_a
end

def ephemera_projects
@ephemera_projects ||= query_service.find_parents(resource: model).map(&:decorate).to_a
end

def collection_slugs
@collection_slugs ||= ephemera_projects.map(&:slug)
end

def ephemera_project
@ephemera_box ||= query_service.find_parents(resource: model).to_a.first.try(:decorate) || NullProject.new
end
Expand Down
4 changes: 4 additions & 0 deletions app/decorators/ephemera_field_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ class EphemeraFieldDecorator < Valkyrie::ResourceDecorator

# Retrieves the EphemeraProjects to which this EphemeraField is linked
# @return [Array<EphemeraProject>]
def parents
@parents ||= query_service.find_parents(resource: model).to_a
end

def projects
@projects ||= parents.select { |r| r.is_a?(EphemeraProject) }.map(&:decorate).to_a
end
Expand Down
15 changes: 11 additions & 4 deletions app/decorators/ephemera_folder_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ class EphemeraFolderDecorator < Valkyrie::ResourceDecorator
]
self.iiif_manifest_attributes = display_attributes + [:title] - \
imported_attributes(Schema::Common.attributes) - \
Schema::IIIF.attributes - \
[:visibility, :internal_resource, :rights_statement, :rendered_rights_statement, :thumbnail_id, :provenance]
Schema::IIIF.attributes - [:visibility, :internal_resource, :rights_statement, :rendered_rights_statement, :thumbnail_id]

def members
@members ||= query_service.find_members(resource: model).to_a
end

def collections
@collections ||= query_service.find_references_by(resource: self, property: :member_of_collection_ids).to_a.map(&:decorate)
Expand Down Expand Up @@ -63,11 +66,15 @@ def rendered_rights_statement
end

def ephemera_box
@ephemera_box ||= query_service.find_parents(resource: model).to_a.first.try(:decorate)
@ephemera_box ||= query_service.find_parents(resource: model).map(&:decorate).to_a.first
end

def ephemera_project
@ephemera_project ||= ephemera_box.ephemera_project
@ephemera_project ||= ephemera_box.try(:ephemera_project)
end

def collection_slugs
@collection_slugs ||= Array.wrap(ephemera_project.try(:slug))
end

def manageable_files?
Expand Down
20 changes: 20 additions & 0 deletions app/decorators/ephemera_project_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
class EphemeraProjectDecorator < Valkyrie::ResourceDecorator
self.display_attributes = [:title]

def members
@members ||= query_service.find_members(resource: model).to_a
end

def boxes
@boxes ||= members.select { |r| r.is_a?(EphemeraBox) }.map(&:decorate).to_a
end
Expand All @@ -25,4 +29,20 @@ def manageable_structure?
def title
super.first
end

def slug
Array.wrap(super).first
end

def iiif_manifest_attributes
local_attributes(self.class.iiif_manifest_attributes).merge iiif_manifest_exhibit
end

private

# Generate the Hash for the IIIF Manifest metadata exposing the slug as an "Exhibit" property
# @return [Hash] the exhibit metadata hash
def iiif_manifest_exhibit
{ exhibit: slug }
end
end
8 changes: 8 additions & 0 deletions app/decorators/file_set_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ def manageable_files?
false
end

def parents
query_service.find_parents(resource: model).to_a.map(&:decorate)
end

def parent
parents.first
end

def collections
[]
end

def collection_slugs
@collection_slugs ||= parent.try(:collection_slugs)
end
end
6 changes: 6 additions & 0 deletions app/decorators/index_collection_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true
class IndexCollectionDecorator < Valkyrie::ResourceDecorator
def iiif_manifest_attributes
[]
end
end
4 changes: 4 additions & 0 deletions app/decorators/scanned_map_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class ScannedMapDecorator < Valkyrie::ResourceDecorator
self.iiif_manifest_attributes = display_attributes + [:title] - \
Schema::IIIF.attributes - [:visibility, :internal_resource, :rights_statement, :rendered_rights_statement, :thumbnail_id]

def members
@members ||= query_service.find_members(resource: model).to_a
end

def scanned_map_members
@scanned_maps ||= members.select { |r| r.is_a?(ScannedMap) }.map(&:decorate).to_a
end
Expand Down
8 changes: 8 additions & 0 deletions app/decorators/scanned_resource_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ class ScannedResourceDecorator < Valkyrie::ResourceDecorator
Schema::IIIF.attributes - [:visibility, :internal_resource, :rights_statement, :rendered_rights_statement, :thumbnail_id]
delegate(*Schema::Common.attributes, to: :primary_imported_metadata, prefix: :imported)

def members
@members ||= query_service.find_members(resource: model).to_a
end

def volumes
@volumes ||= members.select { |r| r.is_a?(ScannedResource) }.map(&:decorate).to_a
end
Expand Down Expand Up @@ -65,6 +69,10 @@ def parents
end
alias collections parents

def collection_slugs
@collection_slugs ||= collections.map(&:slug)
end

def display_imported_language
(imported_language || []).map do |language|
ControlledVocabulary.for(:language).find(language).label
Expand Down
4 changes: 4 additions & 0 deletions app/decorators/vector_work_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
class VectorWorkDecorator < Valkyrie::ResourceDecorator
self.display_attributes += Schema::Geo.attributes + [:rendered_coverage, :member_of_collections] - [:thumbnail_id, :coverage]

def members
@members ||= query_service.find_members(resource: model).to_a
end

# Use case for nesting vector works
# - time series: e.g., nyc transit system, released every 6 months
def vector_work_members
Expand Down
5 changes: 5 additions & 0 deletions app/models/ephemera_project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ class EphemeraProject < Valhalla::Resource
attribute :id, Valkyrie::Types::ID.optional
attribute :member_ids, Valkyrie::Types::Array
attribute :title, Valkyrie::Types::Set
attribute :slug, Valkyrie::Types::Set

def logical_structure
[]
end
end
10 changes: 10 additions & 0 deletions app/models/index_collection.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# frozen_string_literal: true
# Model for exposing IIIF Manifests for all Collection resources
class IndexCollection
# Decorates the object (as this is not a Valkyrie::Resource)
# @return [IndexCollectionDecorator] an instance of the decorator
def decorate
IndexCollectionDecorator.new(self)
end

def logical_structure
[]
end
end
45 changes: 23 additions & 22 deletions app/services/manifest_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ def build
# Presenter modeling the Resource subjects as root nodes
class RootNode
def self.for(resource)
if resource.is_a?(Collection)
case resource
when Collection
CollectionNode.new(resource)
elsif resource.is_a?(IndexCollection)
when IndexCollection
IndexCollectionNode.new(resource)
elsif resource.is_a?(ScannedMap)
when ScannedMap
ScannedMapNode.new(resource)
else
new(resource)
Expand All @@ -48,7 +49,12 @@ def to_s
end

def description
value = resource.respond_to?(:primary_imported_metadata) ? resource.primary_imported_metadata.description : resource.description
value = if resource.respond_to?(:primary_imported_metadata)
resource.primary_imported_metadata.try(:description)
else
resource.try(:description)
end

Array.wrap(value).first
end

Expand Down Expand Up @@ -106,7 +112,7 @@ def helper
# Retrieve the child members for the subject resource of the Manifest
# @return [Resource]
def members
@members ||= query_service.find_members(resource: resource).to_a
@members ||= decorate.members
end

##
Expand Down Expand Up @@ -136,32 +142,20 @@ def file_set_presenters
[]
end

def members
@members ||= query_service.find_inverse_references_by(resource: resource, property: :member_of_collection_ids).to_a
end

def viewing_hint
nil
end

def description
resource.description
end
end

class IndexCollectionNode < RootNode
def file_set_presenters
[]
def viewing_hint
'multi-part'
end
end

class IndexCollectionNode < CollectionNode
def members
@members ||= query_service.find_all_of_model(model: Collection).to_a
end

def viewing_hint
nil
end

def manifest_url
helper.index_manifest_url
end
Expand All @@ -173,6 +167,10 @@ def to_s
def description
"All collections which are a part of Plum."
end

def id
nil
end
end

class ScannedMapNode < RootNode
Expand Down Expand Up @@ -328,8 +326,11 @@ def default_url_options
end

def manifest_url(resource)
if resource.is_a?(Collection)
case resource
when Collection
"#{protocol}://#{host}/collections/#{resource.id}/manifest"
when FileSet
"#{protocol}://#{host}/concern/#{resource.decorate.parent.model_name.plural}/#{resource.decorate.parent.id}/manifest"
else
"#{protocol}://#{host}/concern/#{resource.model_name.plural}/#{resource.id}/manifest"
end
Expand Down
13 changes: 13 additions & 0 deletions app/services/manifest_builder/manifest_service_locator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ def child_collection_builder
)
end

# Provides the builders injected into the factory for manifests of collections
# @see IIIFManifest::ManifestServiceLocator#collection_manifest_builder
# @return [IIIFManifest::ManifestBuilder::CompositeBuilderFactory] the factory of multiple builders
def collection_manifest_builders
composite_builder_factory.new(
record_property_builder,
child_manifest_builder_factory,
metadata_manifest_builder,
see_also_builder,
composite_builder: composite_builder
)
end

class CollectionManifestBuilder < IIIFManifest::ManifestBuilder
def apply(collection)
collection['collections'] ||= []
Expand Down
Loading

0 comments on commit 4eeb873

Please sign in to comment.