speedy-af 0.2.0 → 0.4.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 +4 -4
- data/.circleci/config.yml +20 -8
- data/.rubocop_todo.yml +15 -2
- data/Rakefile +9 -0
- data/lib/speedy_af/base.rb +155 -37
- data/lib/speedy_af/version.rb +1 -1
- data/spec/integration/base_spec.rb +49 -1
- data/speedy-af.gemspec +1 -1
- metadata +3 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0fd6d0ac71c3da9c7807fe63bf98774a75abf4796130be797e19a9eba4ac6292
|
4
|
+
data.tar.gz: 549a1835b24011c7ebebef142654afb7c8ab0afdd0b70e39a713e7e1c9055dca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50e4f754a2f59937b4d08a3ccd9d460e64ecbba50d1e5eed821a28fcb77aab99f77a62307c9185269d27ac1f0baab58e72f8e56cc9debbab71bd99c2c54700e5
|
7
|
+
data.tar.gz: f0f1e0fd4e04f475085ada08769cf9617a95f6ddcd2b0955cea44adc91284844862d0c3c6729c9bc17c32ba456067e448d83c81be9899520003b5dea3b142ed1
|
data/.circleci/config.yml
CHANGED
@@ -10,14 +10,14 @@ jobs:
|
|
10
10
|
type: string
|
11
11
|
bundler_version:
|
12
12
|
type: string
|
13
|
-
default: 2.
|
13
|
+
default: 2.5.18
|
14
14
|
ffmpeg_version:
|
15
15
|
type: string
|
16
16
|
default: 4.1.4
|
17
17
|
executor:
|
18
18
|
name: 'samvera/ruby_fcrepo_solr'
|
19
19
|
ruby_version: << parameters.ruby_version >>
|
20
|
-
solr_version:
|
20
|
+
solr_version: 8-slim
|
21
21
|
environment:
|
22
22
|
RAILS_VERSION: << parameters.rails_version >>
|
23
23
|
working_directory: ~/project
|
@@ -38,10 +38,22 @@ workflows:
|
|
38
38
|
ci:
|
39
39
|
jobs:
|
40
40
|
- bundle_and_test:
|
41
|
-
name: "
|
42
|
-
ruby_version: "
|
43
|
-
rails_version: "
|
41
|
+
name: "ruby3-3_rails7-2"
|
42
|
+
ruby_version: "3.3.4"
|
43
|
+
rails_version: "7.2.1"
|
44
44
|
- bundle_and_test:
|
45
|
-
name: "
|
46
|
-
ruby_version: "2.
|
47
|
-
rails_version: "
|
45
|
+
name: "ruby3-2_rails7-1"
|
46
|
+
ruby_version: "3.2.5"
|
47
|
+
rails_version: "7.1.4"
|
48
|
+
- bundle_and_test:
|
49
|
+
name: "ruby3-1_rails7-1"
|
50
|
+
ruby_version: "3.1.6"
|
51
|
+
rails_version: "7.1.4"
|
52
|
+
- bundle_and_test:
|
53
|
+
name: "ruby3-2_rails7-0"
|
54
|
+
ruby_version: "3.2.5"
|
55
|
+
rails_version: "7.0.8.4"
|
56
|
+
- bundle_and_test:
|
57
|
+
name: "ruby3-1_rails7-0"
|
58
|
+
ruby_version: "3.1.6"
|
59
|
+
rails_version: "7.0.8.4"
|
data/.rubocop_todo.yml
CHANGED
@@ -11,21 +11,34 @@
|
|
11
11
|
# ExcludedMethods: refine
|
12
12
|
Metrics/BlockLength:
|
13
13
|
Max: 143
|
14
|
+
Exclude:
|
15
|
+
- 'spec/**/*_spec.rb'
|
14
16
|
|
15
17
|
# Offense count: 1
|
16
18
|
# Configuration parameters: CountComments.
|
17
19
|
Metrics/ClassLength:
|
18
20
|
Max: 165
|
21
|
+
Exclude:
|
22
|
+
- 'lib/speedy_af/base.rb'
|
19
23
|
|
20
24
|
# Offense count: 1
|
21
25
|
# Configuration parameters: IgnoredMethods.
|
22
26
|
Metrics/CyclomaticComplexity:
|
23
|
-
Max:
|
27
|
+
Max: 11
|
28
|
+
|
29
|
+
# Offense count: 1
|
30
|
+
# Configuration parameters: IgnoredMethods.
|
31
|
+
Metrics/PerceivedComplexity:
|
32
|
+
Max: 11
|
33
|
+
|
34
|
+
# Offense count: 1
|
35
|
+
Metrics/AbcSize:
|
36
|
+
Max: 40
|
24
37
|
|
25
38
|
# Offense count: 1
|
26
39
|
# Configuration parameters: CountComments, ExcludedMethods.
|
27
40
|
Metrics/MethodLength:
|
28
|
-
Max:
|
41
|
+
Max: 20
|
29
42
|
|
30
43
|
# Offense count: 1
|
31
44
|
# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, Regex, IgnoreExecutableScripts, AllowedAcronyms.
|
data/Rakefile
CHANGED
@@ -5,6 +5,15 @@ require 'active_fedora/rake_support'
|
|
5
5
|
require 'bundler'
|
6
6
|
Bundler::GemHelper.install_tasks
|
7
7
|
|
8
|
+
# This is to fix a breaking change with Ruby 3.2 where some methods
|
9
|
+
# in fcrepo_wrapper use the removed `File.exists?` alias, preventing
|
10
|
+
# tests from being set up.
|
11
|
+
class File
|
12
|
+
class << self
|
13
|
+
alias exists? exist?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
8
17
|
require 'rspec/core/rake_task'
|
9
18
|
desc 'Run tests only'
|
10
19
|
RSpec::Core::RakeTask.new(:rspec) do |spec|
|
data/lib/speedy_af/base.rb
CHANGED
@@ -7,55 +7,154 @@ module SpeedyAF
|
|
7
7
|
|
8
8
|
SOLR_ALL = 10_000_000
|
9
9
|
|
10
|
+
class_attribute :model_reflections, :reflection_predicates
|
11
|
+
SpeedyAF::Base.model_reflections = { belongs_to: {}, has_many: {}, subresource: {} }
|
12
|
+
SpeedyAF::Base.reflection_predicates = {}
|
13
|
+
|
10
14
|
attr_reader :attrs, :model
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
class << self
|
17
|
+
def defaults
|
18
|
+
@defaults ||= {}
|
19
|
+
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
def defaults=(value)
|
22
|
+
raise ArgumentError unless value.respond_to?(:merge)
|
23
|
+
@defaults = value
|
24
|
+
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def proxy_class_for(model)
|
27
|
+
klass = "::SpeedyAF::Proxy::#{model.name}".safe_constantize
|
28
|
+
if klass.nil?
|
29
|
+
namespace = model.name.deconstantize
|
30
|
+
name = model.name.demodulize
|
31
|
+
klass_module = namespace.split(/::/).inject(::SpeedyAF::Proxy) do |mod, ns|
|
32
|
+
mod.const_defined?(ns, false) ? mod.const_get(ns, false) : mod.const_set(ns, Module.new)
|
33
|
+
end
|
34
|
+
klass = klass_module.const_set(name, Class.new(self))
|
28
35
|
end
|
29
|
-
klass
|
36
|
+
klass
|
30
37
|
end
|
31
|
-
klass
|
32
|
-
end
|
33
38
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
39
|
+
def config(model, &block)
|
40
|
+
proxy_class = proxy_class_for(model) { Class.new(self) }
|
41
|
+
proxy_class.class_eval(&block) if block_given?
|
42
|
+
end
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
|
44
|
+
def find(id, opts = {})
|
45
|
+
where(%(id:"#{id}"), opts.merge(rows: 1)).first
|
46
|
+
end
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
48
|
+
def where(query, opts = {})
|
49
|
+
docs = ActiveFedora::SolrService.query(query, rows: opts[:rows] || SOLR_ALL)
|
50
|
+
from(docs, opts)
|
51
|
+
end
|
47
52
|
|
48
|
-
|
49
|
-
hash = docs.each_with_object({}) do |doc, h|
|
53
|
+
def for(doc, opts = {})
|
50
54
|
proxy = proxy_class_for(model_for(doc))
|
51
|
-
|
55
|
+
proxy.new(doc, opts[:defaults])
|
52
56
|
end
|
53
|
-
return hash.values if opts[:order].nil?
|
54
|
-
opts[:order].call.collect { |id| hash[id] }.to_a
|
55
|
-
end
|
56
57
|
|
57
|
-
|
58
|
-
|
58
|
+
def from(docs, opts = {})
|
59
|
+
hash = docs.each_with_object({}) do |doc, h|
|
60
|
+
h[doc['id']] = self.for(doc, opts)
|
61
|
+
end
|
62
|
+
|
63
|
+
if opts[:load_reflections]
|
64
|
+
reflections_hash = gather_reflections(hash, opts)
|
65
|
+
reflections_hash.each { |parent_id, reflections| hash[parent_id].attrs.merge!(reflections) }
|
66
|
+
end
|
67
|
+
|
68
|
+
return hash.values if opts[:order].nil?
|
69
|
+
opts[:order].call.collect { |id| hash[id] }.to_a
|
70
|
+
end
|
71
|
+
|
72
|
+
def model_for(solr_document)
|
73
|
+
solr_document[:has_model_ssim].first.safe_constantize
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def predicate_for_reflection(reflection)
|
79
|
+
SpeedyAF::Base.reflection_predicates[reflection.name] ||= reflection.predicate_for_solr
|
80
|
+
end
|
81
|
+
|
82
|
+
def gather_reflections(proxy_hash, opts)
|
83
|
+
query = [query_for_belongs_to(proxy_hash, opts), query_for_has_many(proxy_hash, opts), query_for_subresources(proxy_hash, opts)].reject(&:blank?).join(" OR ")
|
84
|
+
docs = ActiveFedora::SolrService.query query, rows: SOLR_ALL
|
85
|
+
|
86
|
+
reflections = {}
|
87
|
+
reflections.deep_merge!(gather_belongs_to(docs, proxy_hash, opts))
|
88
|
+
reflections.deep_merge!(gather_has_many(docs, proxy_hash, opts))
|
89
|
+
reflections.deep_merge!(gather_subresources(docs, proxy_hash, opts))
|
90
|
+
reflections
|
91
|
+
end
|
92
|
+
|
93
|
+
def query_for_belongs_to(proxy_hash, _opts)
|
94
|
+
proxy_hash.collect do |_id, proxy|
|
95
|
+
proxy.belongs_to_reflections.collect do |_name, reflection|
|
96
|
+
id = proxy.attrs[predicate_for_reflection(reflection).to_sym]
|
97
|
+
id.blank? ? nil : "id:#{id}"
|
98
|
+
end.compact
|
99
|
+
end.flatten.join(" OR ")
|
100
|
+
end
|
101
|
+
|
102
|
+
def gather_belongs_to(docs, proxy_hash, _opts)
|
103
|
+
hash = {}
|
104
|
+
proxy_hash.each do |proxy_id, proxy|
|
105
|
+
proxy.belongs_to_reflections.each do |name, reflection|
|
106
|
+
doc = docs.find { |d| d.id == proxy.attrs[predicate_for_reflection(reflection).to_sym] }
|
107
|
+
next unless doc
|
108
|
+
hash[proxy_id] ||= {}
|
109
|
+
hash[proxy_id][name] = doc
|
110
|
+
hash[proxy_id]["#{name}_id".to_sym] = doc.id
|
111
|
+
end
|
112
|
+
end
|
113
|
+
hash
|
114
|
+
end
|
115
|
+
|
116
|
+
def query_for_has_many(proxy_hash, _opts)
|
117
|
+
proxy_hash.collect do |id, proxy|
|
118
|
+
proxy.has_many_reflections.collect { |_name, reflection| "#{predicate_for_reflection(reflection)}_ssim:#{id}" }
|
119
|
+
end.flatten.join(" OR ")
|
120
|
+
end
|
121
|
+
|
122
|
+
def gather_has_many(docs, proxy_hash, _opts)
|
123
|
+
hash = {}
|
124
|
+
has_many_reflections = SpeedyAF::Base.model_reflections[:has_many].values.reduce(:merge)
|
125
|
+
docs.each do |doc|
|
126
|
+
has_many_reflections.each do |name, reflection|
|
127
|
+
Array(doc["#{predicate_for_reflection(reflection)}_ssim"]).each do |proxy_id|
|
128
|
+
next unless proxy_hash.key?(proxy_id)
|
129
|
+
hash[proxy_id] ||= {}
|
130
|
+
hash[proxy_id][name] ||= []
|
131
|
+
hash[proxy_id][name] << doc
|
132
|
+
hash[proxy_id]["#{name.to_s.singularize}_ids".to_sym] ||= []
|
133
|
+
hash[proxy_id]["#{name.to_s.singularize}_ids".to_sym] << doc.id
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
hash
|
138
|
+
end
|
139
|
+
|
140
|
+
def query_for_subresources(proxy_hash, _opts)
|
141
|
+
proxy_hash.collect do |id, proxy|
|
142
|
+
proxy.subresource_reflections.collect { |name, _reflection| "id:#{id}/#{name}" }
|
143
|
+
end.flatten.join(" OR ")
|
144
|
+
end
|
145
|
+
|
146
|
+
def gather_subresources(docs, proxy_hash, _opts)
|
147
|
+
docs.each_with_object({}) do |doc, hash|
|
148
|
+
doc_id = doc.id
|
149
|
+
parent_id = proxy_hash.keys.find { |id| doc_id.start_with? id }
|
150
|
+
next unless parent_id
|
151
|
+
subresource_id = proxy_hash[parent_id].subresource_reflections.keys.find { |name| doc_id == "#{parent_id}/#{name}" }
|
152
|
+
next unless subresource_id
|
153
|
+
hash[parent_id] ||= {}
|
154
|
+
hash[parent_id][subresource_id.to_sym] = doc
|
155
|
+
hash[parent_id]["#{subresource_id}_id".to_sym] = doc_id
|
156
|
+
end
|
157
|
+
end
|
59
158
|
end
|
60
159
|
|
61
160
|
def initialize(solr_document, instance_defaults = {})
|
@@ -101,7 +200,12 @@ module SpeedyAF
|
|
101
200
|
def method_missing(sym, *args)
|
102
201
|
return real_object.send(sym, *args) if real?
|
103
202
|
|
104
|
-
|
203
|
+
if @attrs.key?(sym)
|
204
|
+
# Lazy convert the solr document into a speedy_af proxy object
|
205
|
+
@attrs[sym] = SpeedyAF::Base.for(@attrs[sym]) if @attrs[sym].is_a?(ActiveFedora::SolrHit)
|
206
|
+
@attrs[sym] = @attrs[sym].map { |doc| SpeedyAF::Base.for(doc) } if @attrs[sym].is_a?(Array) && @attrs[sym].all? { |d| d.is_a?(ActiveFedora::SolrHit) }
|
207
|
+
return @attrs[sym]
|
208
|
+
end
|
105
209
|
|
106
210
|
reflection = reflection_for(sym)
|
107
211
|
unless reflection.nil?
|
@@ -119,6 +223,20 @@ module SpeedyAF
|
|
119
223
|
super
|
120
224
|
end
|
121
225
|
|
226
|
+
def subresource_reflections
|
227
|
+
SpeedyAF::Base.model_reflections[:subresource][model] ||= model.reflections.select { |_name, reflection| reflection.is_a? ActiveFedora::Reflection::HasSubresourceReflection }
|
228
|
+
end
|
229
|
+
|
230
|
+
# rubocop:disable Naming/PredicateName
|
231
|
+
def has_many_reflections
|
232
|
+
SpeedyAF::Base.model_reflections[:has_many][model] ||= model.reflections.select { |_name, reflection| reflection.has_many? && reflection.respond_to?(:predicate_for_solr) }
|
233
|
+
end
|
234
|
+
# rubocop:enable Naming/PredicateName
|
235
|
+
|
236
|
+
def belongs_to_reflections
|
237
|
+
SpeedyAF::Base.model_reflections[:belongs_to][model] ||= model.reflections.select { |_name, reflection| reflection.belongs_to? && reflection.respond_to?(:predicate_for_solr) }
|
238
|
+
end
|
239
|
+
|
122
240
|
protected
|
123
241
|
|
124
242
|
def reflection_for(sym)
|
data/lib/speedy_af/version.rb
CHANGED
@@ -61,7 +61,7 @@ describe SpeedyAF::Base do
|
|
61
61
|
end
|
62
62
|
|
63
63
|
it '.to_query' do
|
64
|
-
expect(book.to_query('book_id')).to eq("book_id=#{URI.
|
64
|
+
expect(book.to_query('book_id')).to eq("book_id=#{URI::Parser.new.escape(book.id, /[^\-_.!~*'()a-zA-Z\d;?:@&=+$,\[\]]/)}")
|
65
65
|
end
|
66
66
|
|
67
67
|
context 'reflections' do
|
@@ -104,6 +104,54 @@ describe SpeedyAF::Base do
|
|
104
104
|
expect(book_presenter.library.model).to eq(library.class)
|
105
105
|
expect(book_presenter).not_to be_real
|
106
106
|
end
|
107
|
+
|
108
|
+
context 'missing parent id' do
|
109
|
+
let(:book) { Book.new title: 'Ordered Things', publisher: 'ActiveFedora Performance LLC', library: nil }
|
110
|
+
it 'does not cause belongs_to reflections to error' do
|
111
|
+
expect(book_presenter.library_id).to be_nil
|
112
|
+
expect(book_presenter.library).to be_nil
|
113
|
+
expect(book_presenter).not_to be_real
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'preloaded subresources' do
|
118
|
+
let(:book_presenter) { described_class.find(book.id, load_reflections: true) }
|
119
|
+
|
120
|
+
it 'has already loaded indexed subresources' do
|
121
|
+
expect(book_presenter.attrs).to include :indexed_file
|
122
|
+
allow(ActiveFedora::SolrService).to receive(:query).and_call_original
|
123
|
+
ipsum_presenter = book_presenter.indexed_file
|
124
|
+
expect(ipsum_presenter.model).to eq(IndexedFile)
|
125
|
+
expect(ipsum_presenter.content).to eq(indexed_content)
|
126
|
+
expect(ipsum_presenter).not_to be_real
|
127
|
+
expect(ActiveFedora::SolrService).not_to have_received(:query)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'has already loaded has_many reflections' do
|
131
|
+
library.books.create(title: 'Ordered Things II')
|
132
|
+
library.save
|
133
|
+
book_ids = library.book_ids
|
134
|
+
library_presenter = described_class.find(library.id, load_reflections: true)
|
135
|
+
expect(library_presenter.attrs).to include :books
|
136
|
+
allow(ActiveFedora::SolrService).to receive(:query).and_call_original
|
137
|
+
presenter = library_presenter.books
|
138
|
+
expect(presenter.length).to eq(2)
|
139
|
+
expect(presenter.all? { |bp| bp.is_a?(described_class) }).to be_truthy
|
140
|
+
expect(library_presenter.book_ids).to match_array(book_ids)
|
141
|
+
expect(library_presenter).not_to be_real
|
142
|
+
expect(ActiveFedora::SolrService).not_to have_received(:query)
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'has already loaded belongs_to reflections' do
|
146
|
+
expect(book_presenter.attrs).to include :library
|
147
|
+
allow(ActiveFedora::SolrService).to receive(:query).and_call_original
|
148
|
+
expect(book_presenter.library_id).to eq(library.id)
|
149
|
+
expect(book_presenter.library).to be_a(described_class)
|
150
|
+
expect(book_presenter.library.model).to eq(library.class)
|
151
|
+
expect(book_presenter).not_to be_real
|
152
|
+
expect(ActiveFedora::SolrService).not_to have_received(:query)
|
153
|
+
end
|
154
|
+
end
|
107
155
|
end
|
108
156
|
|
109
157
|
context 'configuration' do
|
data/speedy-af.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
25
25
|
|
26
26
|
s.add_dependency 'active-fedora', '>= 11.0.0'
|
27
|
-
s.add_dependency 'activesupport', '> 5.2'
|
27
|
+
s.add_dependency 'activesupport', '> 5.2'
|
28
28
|
s.add_development_dependency 'solr_wrapper'
|
29
29
|
s.add_development_dependency 'fcrepo_wrapper'
|
30
30
|
s.add_development_dependency 'simplecov'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: speedy-af
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael B. Klein
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: active-fedora
|
@@ -31,9 +31,6 @@ dependencies:
|
|
31
31
|
- - ">"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '5.2'
|
34
|
-
- - "<"
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: '6.1'
|
37
34
|
type: :runtime
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -41,9 +38,6 @@ dependencies:
|
|
41
38
|
- - ">"
|
42
39
|
- !ruby/object:Gem::Version
|
43
40
|
version: '5.2'
|
44
|
-
- - "<"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '6.1'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: solr_wrapper
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -232,7 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
232
226
|
- !ruby/object:Gem::Version
|
233
227
|
version: '0'
|
234
228
|
requirements: []
|
235
|
-
rubygems_version: 3.
|
229
|
+
rubygems_version: 3.5.16
|
236
230
|
signing_key:
|
237
231
|
specification_version: 4
|
238
232
|
summary: Performance enhancements for ActiveFedora
|