speedy-af 0.2.0 → 0.3.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: 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