speedy-af 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ed5f4d42ff93fc2fb41bbc8096025cd873767f3f01e9182ea87f309dbb7f6b0
4
- data.tar.gz: def94b1cb7b8f0dd8e8241d4e8dba0b1f80e849cb10ebe0346d788f18bf8b3b2
3
+ metadata.gz: f90da5142141870db8a58733e7456af65d963a3de8ed26e40af1c1d23fba022a
4
+ data.tar.gz: a995000c4e447c04073f22884d7608c33fb9a6401d839ef613007818ebb4d648
5
5
  SHA512:
6
- metadata.gz: 2607efe588e3b14c24712c27cfa847be6eec358abc77adbe0e47d0f68ac93e7a551184c4ac9b5486e3ba75b41989b85cf2d3a865fe00c8a0aed1460abb1b1839
7
- data.tar.gz: e79f5bb789b2527dbd728eae084ba42600d637a8284818eedbed2a7c2af2d1a7068b50830c35b6092d9f911c25df31dc49e2da2a60e0a934851200f050306b69
6
+ metadata.gz: d07dc9fdfabc614b2ad7ba5960da463d102f07680626efa40e1446087ea027b243fe29ac28be4ad01866c5edbff3663ee9bc846297c3bd3815fc481dd7564b90
7
+ data.tar.gz: 445ce106eabd70703ad9b96ef3c91a657fd6500d82e8b7c9d8e3c52baf48030d01601fc21f95ac57a5832f721621db8aa535ddf2bac546381d2c0e0b9eacdb29
data/.circleci/config.yml CHANGED
@@ -17,7 +17,7 @@ jobs:
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
@@ -37,11 +37,19 @@ jobs:
37
37
  workflows:
38
38
  ci:
39
39
  jobs:
40
+ - bundle_and_test:
41
+ name: "ruby2-7_rails7-0"
42
+ ruby_version: "2.7.6"
43
+ rails_version: "7.0.4"
44
+ - bundle_and_test:
45
+ name: "ruby2-7_rails6-1"
46
+ ruby_version: "2.7.6"
47
+ rails_version: "6.1.7"
40
48
  - bundle_and_test:
41
49
  name: "ruby2-7_rails6-0"
42
- ruby_version: "2.7.5"
43
- rails_version: "6.0.4.4"
50
+ ruby_version: "2.7.6"
51
+ rails_version: "6.0.6"
44
52
  - bundle_and_test:
45
53
  name: "ruby2-7_rails5-2"
46
- ruby_version: "2.7.5"
47
- rails_version: "5.2.6"
54
+ ruby_version: "2.7.6"
55
+ rails_version: "5.2.8.1"
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.
@@ -7,55 +7,151 @@ 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).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: 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 { |_name, reflection| "id:#{proxy.attrs[predicate_for_reflection(reflection).to_sym]}" }
96
+ end.flatten.join(" OR ")
97
+ end
98
+
99
+ def gather_belongs_to(docs, proxy_hash, _opts)
100
+ hash = {}
101
+ proxy_hash.each do |proxy_id, proxy|
102
+ proxy.belongs_to_reflections.each do |name, reflection|
103
+ doc = docs.find { |d| d.id == proxy.attrs[predicate_for_reflection(reflection).to_sym] }
104
+ next unless doc
105
+ hash[proxy_id] ||= {}
106
+ hash[proxy_id][name] = doc
107
+ hash[proxy_id]["#{name}_id".to_sym] = doc.id
108
+ end
109
+ end
110
+ hash
111
+ end
112
+
113
+ def query_for_has_many(proxy_hash, _opts)
114
+ proxy_hash.collect do |id, proxy|
115
+ proxy.has_many_reflections.collect { |_name, reflection| "#{predicate_for_reflection(reflection)}_ssim:#{id}" }
116
+ end.flatten.join(" OR ")
117
+ end
118
+
119
+ def gather_has_many(docs, proxy_hash, _opts)
120
+ hash = {}
121
+ has_many_reflections = SpeedyAF::Base.model_reflections[:has_many].values.reduce(:merge)
122
+ docs.each do |doc|
123
+ has_many_reflections.each do |name, reflection|
124
+ Array(doc["#{predicate_for_reflection(reflection)}_ssim"]).each do |proxy_id|
125
+ next unless proxy_hash.key?(proxy_id)
126
+ hash[proxy_id] ||= {}
127
+ hash[proxy_id][name] ||= []
128
+ hash[proxy_id][name] << doc
129
+ hash[proxy_id]["#{name.to_s.singularize}_ids".to_sym] ||= []
130
+ hash[proxy_id]["#{name.to_s.singularize}_ids".to_sym] << doc.id
131
+ end
132
+ end
133
+ end
134
+ hash
135
+ end
136
+
137
+ def query_for_subresources(proxy_hash, _opts)
138
+ proxy_hash.collect do |id, proxy|
139
+ proxy.subresource_reflections.collect { |name, _reflection| "id:#{id}/#{name}" }
140
+ end.flatten.join(" OR ")
141
+ end
142
+
143
+ def gather_subresources(docs, proxy_hash, _opts)
144
+ docs.each_with_object({}) do |doc, hash|
145
+ doc_id = doc.id
146
+ parent_id = proxy_hash.keys.find { |id| doc_id.start_with? id }
147
+ next unless parent_id
148
+ subresource_id = proxy_hash[parent_id].subresource_reflections.keys.find { |name| doc_id == "#{parent_id}/#{name}" }
149
+ next unless subresource_id
150
+ hash[parent_id] ||= {}
151
+ hash[parent_id][subresource_id.to_sym] = doc
152
+ hash[parent_id]["#{subresource_id}_id".to_sym] = doc_id
153
+ end
154
+ end
59
155
  end
60
156
 
61
157
  def initialize(solr_document, instance_defaults = {})
@@ -101,7 +197,12 @@ module SpeedyAF
101
197
  def method_missing(sym, *args)
102
198
  return real_object.send(sym, *args) if real?
103
199
 
104
- return @attrs[sym] if @attrs.key?(sym)
200
+ if @attrs.key?(sym)
201
+ # Lazy convert the solr document into a speedy_af proxy object
202
+ @attrs[sym] = SpeedyAF::Base.for(@attrs[sym]) if @attrs[sym].is_a?(ActiveFedora::SolrHit)
203
+ @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) }
204
+ return @attrs[sym]
205
+ end
105
206
 
106
207
  reflection = reflection_for(sym)
107
208
  unless reflection.nil?
@@ -119,6 +220,20 @@ module SpeedyAF
119
220
  super
120
221
  end
121
222
 
223
+ def subresource_reflections
224
+ SpeedyAF::Base.model_reflections[:subresource][model] ||= model.reflections.select { |_name, reflection| reflection.is_a? ActiveFedora::Reflection::HasSubresourceReflection }
225
+ end
226
+
227
+ # rubocop:disable Naming/PredicateName
228
+ def has_many_reflections
229
+ SpeedyAF::Base.model_reflections[:has_many][model] ||= model.reflections.select { |_name, reflection| reflection.has_many? && reflection.respond_to?(:predicate_for_solr) }
230
+ end
231
+ # rubocop:enable Naming/PredicateName
232
+
233
+ def belongs_to_reflections
234
+ SpeedyAF::Base.model_reflections[:belongs_to][model] ||= model.reflections.select { |_name, reflection| reflection.belongs_to? && reflection.respond_to?(:predicate_for_solr) }
235
+ end
236
+
122
237
  protected
123
238
 
124
239
  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.3.0'
4
4
  end
@@ -104,6 +104,45 @@ 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 'preloaded subresources' do
109
+ let(:book_presenter) { described_class.find(book.id, load_reflections: true) }
110
+
111
+ it 'has already loaded indexed subresources' do
112
+ expect(book_presenter.attrs).to include :indexed_file
113
+ allow(ActiveFedora::SolrService).to receive(:query).and_call_original
114
+ ipsum_presenter = book_presenter.indexed_file
115
+ expect(ipsum_presenter.model).to eq(IndexedFile)
116
+ expect(ipsum_presenter.content).to eq(indexed_content)
117
+ expect(ipsum_presenter).not_to be_real
118
+ expect(ActiveFedora::SolrService).not_to have_received(:query)
119
+ end
120
+
121
+ it 'has already loaded has_many reflections' do
122
+ library.books.create(title: 'Ordered Things II')
123
+ library.save
124
+ book_ids = library.book_ids
125
+ library_presenter = described_class.find(library.id, load_reflections: true)
126
+ expect(library_presenter.attrs).to include :books
127
+ allow(ActiveFedora::SolrService).to receive(:query).and_call_original
128
+ presenter = library_presenter.books
129
+ expect(presenter.length).to eq(2)
130
+ expect(presenter.all? { |bp| bp.is_a?(described_class) }).to be_truthy
131
+ expect(library_presenter.book_ids).to match_array(book_ids)
132
+ expect(library_presenter).not_to be_real
133
+ expect(ActiveFedora::SolrService).not_to have_received(:query)
134
+ end
135
+
136
+ it 'has already loaded belongs_to reflections' do
137
+ expect(book_presenter.attrs).to include :library
138
+ allow(ActiveFedora::SolrService).to receive(:query).and_call_original
139
+ expect(book_presenter.library_id).to eq(library.id)
140
+ expect(book_presenter.library).to be_a(described_class)
141
+ expect(book_presenter.library.model).to eq(library.class)
142
+ expect(book_presenter).not_to be_real
143
+ expect(ActiveFedora::SolrService).not_to have_received(:query)
144
+ end
145
+ end
107
146
  end
108
147
 
109
148
  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.3.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: 2022-10-18 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