speedy-af 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ed5f4d42ff93fc2fb41bbc8096025cd873767f3f01e9182ea87f309dbb7f6b0
4
- data.tar.gz: def94b1cb7b8f0dd8e8241d4e8dba0b1f80e849cb10ebe0346d788f18bf8b3b2
3
+ metadata.gz: 0fd6d0ac71c3da9c7807fe63bf98774a75abf4796130be797e19a9eba4ac6292
4
+ data.tar.gz: 549a1835b24011c7ebebef142654afb7c8ab0afdd0b70e39a713e7e1c9055dca
5
5
  SHA512:
6
- metadata.gz: 2607efe588e3b14c24712c27cfa847be6eec358abc77adbe0e47d0f68ac93e7a551184c4ac9b5486e3ba75b41989b85cf2d3a865fe00c8a0aed1460abb1b1839
7
- data.tar.gz: e79f5bb789b2527dbd728eae084ba42600d637a8284818eedbed2a7c2af2d1a7068b50830c35b6092d9f911c25df31dc49e2da2a60e0a934851200f050306b69
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.0.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: 7-alpine
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: "ruby2-7_rails6-0"
42
- ruby_version: "2.7.5"
43
- rails_version: "6.0.4.4"
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: "ruby2-7_rails5-2"
46
- ruby_version: "2.7.5"
47
- rails_version: "5.2.6"
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: 7
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: 15
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|
@@ -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
- def self.defaults
13
- @defaults ||= {}
14
- end
16
+ class << self
17
+ def defaults
18
+ @defaults ||= {}
19
+ end
15
20
 
16
- def self.defaults=(value)
17
- raise ArgumentError unless value.respond_to?(:merge)
18
- @defaults = value
19
- end
21
+ def defaults=(value)
22
+ raise ArgumentError unless value.respond_to?(:merge)
23
+ @defaults = value
24
+ end
20
25
 
21
- def self.proxy_class_for(model)
22
- klass = "::SpeedyAF::Proxy::#{model.name}".safe_constantize
23
- if klass.nil?
24
- namespace = model.name.deconstantize
25
- name = model.name.demodulize
26
- klass_module = namespace.split(/::/).inject(::SpeedyAF::Proxy) do |mod, ns|
27
- mod.const_defined?(ns, false) ? mod.const_get(ns, false) : mod.const_set(ns, Module.new)
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 = klass_module.const_set(name, Class.new(self))
36
+ klass
30
37
  end
31
- klass
32
- end
33
38
 
34
- def self.config(model, &block)
35
- proxy_class = proxy_class_for(model) { Class.new(self) }
36
- proxy_class.class_eval(&block) if block_given?
37
- end
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
- def self.find(id, opts = {})
40
- where(%(id:"#{id}"), opts).first
41
- end
44
+ def find(id, opts = {})
45
+ where(%(id:"#{id}"), opts.merge(rows: 1)).first
46
+ end
42
47
 
43
- def self.where(query, opts = {})
44
- docs = ActiveFedora::SolrService.query(query, rows: SOLR_ALL)
45
- from(docs, opts)
46
- end
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
- def self.from(docs, opts = {})
49
- hash = docs.each_with_object({}) do |doc, h|
53
+ def for(doc, opts = {})
50
54
  proxy = proxy_class_for(model_for(doc))
51
- h[doc['id']] = proxy.new(doc, opts[:defaults])
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
- def self.model_for(solr_document)
58
- solr_document[:has_model_ssim].first.safe_constantize
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
- return @attrs[sym] if @attrs.key?(sym)
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)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module SpeedyAF
3
- VERSION = '0.2.0'
3
+ VERSION = '0.4.0'
4
4
  end
@@ -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.encode(book.id, /[^\-_.!~*'()a-zA-Z\d;?:@&=+$,\[\]]/)}")
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', '< 6.1'
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.2.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: 2022-06-23 00:00:00.000000000 Z
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.1.6
229
+ rubygems_version: 3.5.16
236
230
  signing_key:
237
231
  specification_version: 4
238
232
  summary: Performance enhancements for ActiveFedora