From b7d556a63af17aca415cac68b338c327d29cf067 Mon Sep 17 00:00:00 2001 From: dleadbetter <> Date: Wed, 8 May 2024 07:27:54 -0400 Subject: [PATCH] IIIF #54 - Adds the "content_type" column to resource_descriptions; Refactors cloud service to not call IIIF Cloud API when loading descriptions --- .ruby-version | 1 + Gemfile.lock | 5 ++- .../resourceable_controller.rb | 9 ++++ .../triple_eye_effable/resourceable.rb | 11 +---- .../resource_description.rb | 17 ++++++- .../resourceable_serializer.rb | 4 +- app/services/triple_eye_effable/cloud.rb | 36 ++++++++------- ...d_content_type_to_resource_descriptions.rb | 5 +++ lib/tasks/triple_eye_effable_tasks.rake | 45 +++++++++++++++---- 9 files changed, 93 insertions(+), 40 deletions(-) create mode 100644 .ruby-version create mode 100644 app/controllers/concerns/triple_eye_effable/resourceable_controller.rb create mode 100644 db/migrate/20240506152120_add_content_type_to_resource_descriptions.rb diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..be94e6f --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.2.2 diff --git a/Gemfile.lock b/Gemfile.lock index dc4631d..611be44 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -96,6 +96,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2022.0105) mini_mime (1.1.2) + mini_portile2 (2.8.6) minitest (5.16.2) multi_xml (0.6.0) net-imap (0.2.3) @@ -113,7 +114,8 @@ GEM net-protocol timeout nio4r (2.5.8) - nokogiri (1.13.8-x86_64-darwin) + nokogiri (1.13.8) + mini_portile2 (~> 2.8.0) racc (~> 1.4) racc (1.6.0) rack (2.2.4) @@ -157,6 +159,7 @@ GEM zeitwerk (2.6.0) PLATFORMS + arm64-darwin-22 x86_64-darwin-19 DEPENDENCIES diff --git a/app/controllers/concerns/triple_eye_effable/resourceable_controller.rb b/app/controllers/concerns/triple_eye_effable/resourceable_controller.rb new file mode 100644 index 0000000..6d25093 --- /dev/null +++ b/app/controllers/concerns/triple_eye_effable/resourceable_controller.rb @@ -0,0 +1,9 @@ +module TripleEyeEffable + module ResourceableController + extend ActiveSupport::Concern + + included do + preloads :resource_description + end + end +end \ No newline at end of file diff --git a/app/models/concerns/triple_eye_effable/resourceable.rb b/app/models/concerns/triple_eye_effable/resourceable.rb index 6ae7525..d34757e 100644 --- a/app/models/concerns/triple_eye_effable/resourceable.rb +++ b/app/models/concerns/triple_eye_effable/resourceable.rb @@ -4,7 +4,7 @@ module Resourceable included do # Relationships - has_one :resource_description, as: :resourceable, dependent: :destroy, class_name: 'TripleEyeEffable::ResourceDescription' + has_one :resource_description, as: :resourceable, dependent: :destroy, class_name: ResourceDescription.to_s # Transient attributes attr_accessor :content @@ -17,11 +17,9 @@ module Resourceable delegate :content_preview_url, to: :resource_description, allow_nil: true delegate :content_thumbnail_url, to: :resource_description, allow_nil: true delegate :content_type, to: :resource_description, allow_nil: true - delegate :manifest, to: :resource_description, allow_nil: true delegate :manifest_url, to: :resource_description, allow_nil: true # Callbacks - after_find :load_resource before_create :create_resource before_destroy :delete_resource before_update :update_resource @@ -42,13 +40,6 @@ def delete_resource throw(:abort) unless self.errors.empty? end - def load_resource - service = TripleEyeEffable::Cloud.new - service.load_resource(self) - - throw(:abort) unless self.errors.empty? - end - def update_resource service = TripleEyeEffable::Cloud.new service.update_resource(self) diff --git a/app/models/triple_eye_effable/resource_description.rb b/app/models/triple_eye_effable/resource_description.rb index b67c7e1..c6bdaa4 100644 --- a/app/models/triple_eye_effable/resource_description.rb +++ b/app/models/triple_eye_effable/resource_description.rb @@ -1,5 +1,8 @@ module TripleEyeEffable class ResourceDescription < ApplicationRecord + # Relationships + belongs_to :resourceable, polymorphic: true + # Transient attributes attr_accessor :content_url attr_accessor :content_download_url @@ -7,11 +10,21 @@ class ResourceDescription < ApplicationRecord attr_accessor :content_inline_url attr_accessor :content_preview_url attr_accessor :content_thumbnail_url - attr_accessor :content_type - attr_accessor :manifest attr_accessor :manifest_url + # Callbacks + after_initialize :load_description + # Validations validates :resource_id, presence: true + + private + + def load_description + service = TripleEyeEffable::Cloud.new + service.load_description(self) + + throw(:abort) unless self.errors.empty? + end end end diff --git a/app/serializers/triple_eye_effable/resourceable_serializer.rb b/app/serializers/triple_eye_effable/resourceable_serializer.rb index d912305..ee852fc 100644 --- a/app/serializers/triple_eye_effable/resourceable_serializer.rb +++ b/app/serializers/triple_eye_effable/resourceable_serializer.rb @@ -4,9 +4,9 @@ module ResourceableSerializer included do index_attributes :content_type, :content_url, :content_download_url, :content_iiif_url, :content_inline_url, - :content_preview_url, :content_thumbnail_url, :manifest, :manifest_url + :content_preview_url, :content_thumbnail_url, :manifest_url show_attributes :content_type, :content_url, :content_download_url, :content_iiif_url, :content_inline_url, - :content_preview_url, :content_thumbnail_url, :manifest, :manifest_url + :content_preview_url, :content_thumbnail_url, :manifest_url end end diff --git a/app/services/triple_eye_effable/cloud.rb b/app/services/triple_eye_effable/cloud.rb index b3f7a88..29fa93b 100644 --- a/app/services/triple_eye_effable/cloud.rb +++ b/app/services/triple_eye_effable/cloud.rb @@ -38,8 +38,8 @@ def create_resource(resourceable) resource_id, data = parse_response(response) - resourceable.resource_description = ResourceDescription.new(resource_id: resource_id) - populate_description resourceable.resource_description, data + resourceable.resource_description = ResourceDescription.new(resource_id: resource_id, content_type: data[:content_type]) + load_description resourceable.resource_description end def delete_resource(resourceable) @@ -52,15 +52,27 @@ def delete_resource(resourceable) add_error(resourceable, response) unless response.success? end - def load_resource(resourceable) - return if resourceable.resource_description.nil? + def download_resource(resourceable) + return if resourceable.nil? || resourceable.resource_description.nil? resource_description = resourceable.resource_description response = self.class.get("#{base_url}/#{resource_description.resource_id}", headers: headers) - add_error(resourceable, response) and return unless response.success? - resource_id, data = parse_response(response) - populate_description resource_description, data unless data.nil? + parse_response(response) + end + + def load_description(resource_description) + id = resource_description.resource_id + + resource_description.assign_attributes( + content_url: "#{base_url}/#{id}/content", + content_download_url: "#{base_url}/#{id}/download", + content_iiif_url: "#{base_url}/#{id}/iiif", + content_inline_url: "#{base_url}/#{id}/inline", + content_preview_url: "#{base_url}/#{id}/preview", + content_thumbnail_url: "#{base_url}/#{id}/thumbnail", + manifest_url: "#{base_url}/#{id}/manifest" + ) end def update_resource(resourceable) @@ -72,8 +84,7 @@ def update_resource(resourceable) response = self.class.put("#{base_url}/#{id}", body: request_body(resourceable), headers: headers) add_error(resourceable, response) and return unless response.success? - resource_id, data = parse_response(response) - populate_description(resource_description, data) + load_description resource_description end def upload_resource(resourceable) @@ -104,13 +115,6 @@ def parse_response(response) [data[:uuid], data.except(:uuid)] end - def populate_description(resource_description, data) - RESPONSE_KEYS.each do |key| - next unless resource_description.respond_to?("#{key.to_s}=") - resource_description.send("#{key.to_s}=", data[key]) - end - end - def request_body(resourceable) name = self.class.filename(resourceable.name) if resourceable.respond_to?(:name) content = resourceable.content if resourceable.respond_to?(:content) diff --git a/db/migrate/20240506152120_add_content_type_to_resource_descriptions.rb b/db/migrate/20240506152120_add_content_type_to_resource_descriptions.rb new file mode 100644 index 0000000..bdccd1b --- /dev/null +++ b/db/migrate/20240506152120_add_content_type_to_resource_descriptions.rb @@ -0,0 +1,5 @@ +class AddContentTypeToResourceDescriptions < ActiveRecord::Migration[7.0] + def change + add_column :triple_eye_effable_resource_descriptions, :content_type, :string + end +end diff --git a/lib/tasks/triple_eye_effable_tasks.rake b/lib/tasks/triple_eye_effable_tasks.rake index 81255ea..4cdd2a1 100644 --- a/lib/tasks/triple_eye_effable_tasks.rake +++ b/lib/tasks/triple_eye_effable_tasks.rake @@ -2,6 +2,37 @@ require 'httparty' require 'optparse' namespace :triple_eye_effable do + desc 'Sets the content_type attribute on each resource record' + task :set_content_type => :environment do + service = TripleEyeEffable::Cloud.new(read_only: true) + + # Build the list of classes that include the Resourceable concern + classes = [] + + Rails.application.eager_load! if Rails.env.development? + + ActiveRecord::Base.descendants.each do |model| + next unless model.include?(TripleEyeEffable::Resourceable) + classes << model + end + + classes.each do |klass| + query = klass + .joins(:resource_description) + .preload(:resource_description) + .where(resource_description: { content_type: nil }) + + query.find_each do |resourceable| + resource_id, data = service.download_resource(resourceable) + content_type = data[:content_type] + + next if content_type.nil? + + resource_description = resourceable.resource_description + resource_description.update_attribute(:content_type, content_type) + end + end + end desc 'Transfer resources from one IIIF Cloud instance to another.' task :transfer_resources => :environment do @@ -42,18 +73,14 @@ namespace :triple_eye_effable do exit 0 end - # Build the list of classes that include the Resourcable concern + # Build the list of classes that include the Resourceable concern classes = [] - ActiveRecord::Base.connection.tables.each do |table_name| - begin - klass = table_name.classify.constantize - next unless klass.ancestors.include?(TripleEyeEffable::Resourceable) + Rails.application.eager_load! if Rails.env.development? - classes << klass - rescue - # Skip the record, there's a chance a table exists with no model - end + ActiveRecord::Base.descendants.each do |model| + next unless model.include?(TripleEyeEffable::Resourceable) + classes << model end source_service = TripleEyeEffable::Cloud.new(