samvera-nesting_indexer 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ require 'samvera/nesting_indexer/exceptions'
2
+ require 'forwardable'
3
+ require 'set'
4
+
5
+ module Samvera
6
+ # Establishing namespace
7
+ module NestingIndexer
8
+ # Responsible for reindexing the PID and its descendants
9
+ # @note There is cycle detection via the TIME_TO_LIVE counter
10
+ # @api private
11
+ class RelationshipReindexer
12
+ # @api private
13
+ #
14
+ # A convenience method that coordinate the relationship reindexing of the given id.
15
+ #
16
+ # @see #initialize
17
+ # @return Samvera::NestingIndexer::RelationshipReindexer
18
+ def self.call(**kwargs)
19
+ new(**kwargs).call
20
+ end
21
+
22
+ # @param id [String]
23
+ # @param maximum_nesting_depth [Integer] Samvera::NestingIndexer::TIME_TO_LIVE to detect cycles in the graph
24
+ # @param adapter [Samvera::NestingIndexer::Adapters::AbstractAdapter] Conforms to the Samvera::NestingIndexer::Adapters::AbstractAdapter interface
25
+ # @param queue [#shift, #push] queue
26
+ def initialize(id:, maximum_nesting_depth:, adapter:, queue: [])
27
+ @id = id.to_s
28
+ @maximum_nesting_depth = maximum_nesting_depth.to_i
29
+ @adapter = adapter
30
+ @queue = queue
31
+ end
32
+ attr_reader :id, :maximum_nesting_depth, :queue, :adapter
33
+
34
+ # Perform a bread-first tree traversal of the initial document and its descendants.
35
+ def call
36
+ enqueue(initial_index_document, maximum_nesting_depth)
37
+ processing_document = dequeue
38
+ while processing_document
39
+ process_a_document(processing_document)
40
+ adapter.each_child_document_of(document: processing_document) { |child| enqueue(child, processing_document.maximum_nesting_depth - 1) }
41
+ processing_document = dequeue
42
+ end
43
+ self
44
+ end
45
+
46
+ private
47
+
48
+ attr_writer :document
49
+
50
+ def initial_index_document
51
+ adapter.find_index_document_by(id: id)
52
+ end
53
+
54
+ extend Forwardable
55
+ def_delegator :queue, :shift, :dequeue
56
+
57
+ require 'delegate'
58
+ # A small object to help track time to live concerns
59
+ class ProcessingDocument < SimpleDelegator
60
+ def initialize(document, maximum_nesting_depth)
61
+ @maximum_nesting_depth = maximum_nesting_depth
62
+ super(document)
63
+ end
64
+ attr_reader :maximum_nesting_depth
65
+ end
66
+ private_constant :ProcessingDocument
67
+ def enqueue(document, maximum_nesting_depth)
68
+ queue.push(ProcessingDocument.new(document, maximum_nesting_depth))
69
+ end
70
+
71
+ def process_a_document(index_document)
72
+ raise Exceptions::CycleDetectionError, id if index_document.maximum_nesting_depth <= 0
73
+ preservation_document = adapter.find_preservation_document_by(id: index_document.id)
74
+ parent_ids_and_path_and_ancestors = parent_ids_and_path_and_ancestors_for(preservation_document)
75
+ adapter.write_document_attributes_to_index_layer(parent_ids_and_path_and_ancestors)
76
+ end
77
+
78
+ def parent_ids_and_path_and_ancestors_for(preservation_document)
79
+ ParentAndPathAndAncestorsBuilder.new(preservation_document, adapter).to_hash
80
+ end
81
+
82
+ # A small object that helps encapsulate the logic of building the hash of information regarding
83
+ # the initialization of an Samvera::NestingIndexer::Documents::IndexDocument
84
+ #
85
+ # @see Samvera::NestingIndexer::Documents::IndexDocument for details on pathnames, ancestors, and parent_ids.
86
+ class ParentAndPathAndAncestorsBuilder
87
+ def initialize(preservation_document, adapter)
88
+ @preservation_document = preservation_document
89
+ @parent_ids = Set.new
90
+ @pathnames = Set.new
91
+ @ancestors = Set.new
92
+ @adapter = adapter
93
+ compile!
94
+ end
95
+
96
+ def to_hash
97
+ { id: @preservation_document.id, parent_ids: @parent_ids.to_a, pathnames: @pathnames.to_a, ancestors: @ancestors.to_a }
98
+ end
99
+
100
+ private
101
+
102
+ attr_reader :adapter
103
+
104
+ def compile!
105
+ @preservation_document.parent_ids.each do |parent_id|
106
+ parent_index_document = adapter.find_index_document_by(id: parent_id)
107
+ compile_one!(parent_index_document)
108
+ end
109
+ # Ensuring that an "orphan" has a path to get to it
110
+ @pathnames << @preservation_document.id if @parent_ids.empty?
111
+ end
112
+
113
+ def compile_one!(parent_index_document)
114
+ @parent_ids << parent_index_document.id
115
+ parent_index_document.pathnames.each do |pathname|
116
+ @pathnames << File.join(pathname, @preservation_document.id)
117
+ slugs = pathname.split("/")
118
+ slugs.each_index { |i| @ancestors << slugs[0..i].join('/') }
119
+ end
120
+ @ancestors += parent_index_document.ancestors
121
+ end
122
+ end
123
+ private_constant :ParentAndPathAndAncestorsBuilder
124
+ end
125
+ private_constant :RelationshipReindexer
126
+ end
127
+ end
@@ -0,0 +1,65 @@
1
+ module Samvera
2
+ # Establishing namespace
3
+ module NestingIndexer
4
+ # Responsible for reindexing the entire repository
5
+ # @api private
6
+ # @note There is cycle detection logic for walking the graph prior to attempting relationship re-indexing
7
+ class RepositoryReindexer
8
+ # @api private
9
+ #
10
+ # A convenience method to reindex all documents.
11
+ #
12
+ # @note This could crush your system as it will loop through ALL the documents
13
+ #
14
+ # @see #initialize
15
+ # @return Samvera::NestingIndexer::RepositoryReindexer
16
+ def self.call(*args)
17
+ new(*args).call
18
+ end
19
+
20
+ # @param id_reindexer [#call] Samvera::NestingIndexer.method(:reindex_relationships) Responsible for reindexing a single object
21
+ # @param maximum_nesting_depth [Integer] detect cycles in the graph
22
+ # @param adapter [Samvera::NestingIndexer::Adapters::AbstractAdapter] Conforms to the Samvera::NestingIndexer::Adapters::AbstractAdapter interface
23
+ def initialize(maximum_nesting_depth:, id_reindexer:, adapter:)
24
+ @max_maximum_nesting_depth = maximum_nesting_depth.to_i
25
+ @id_reindexer = id_reindexer
26
+ @adapter = adapter
27
+ @processed_ids = []
28
+ end
29
+
30
+ # @todo Would it make sense to leverage an each_preservation_id instead?
31
+ def call
32
+ @adapter.each_preservation_document { |document| recursive_reindex(document, max_maximum_nesting_depth) }
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :max_maximum_nesting_depth, :processed_ids, :id_reindexer
38
+
39
+ # When we find a document, reindex it if it doesn't have a parent. If it has a parent, reindex the parent first.
40
+ #
41
+ # Given that we are attempting to reindex the parents before we reindex a document, we can't rely on
42
+ # the reindex maximum_nesting_depth but instead must have a separate time to live.
43
+ #
44
+ # The reindexing process assumes that an object's parents have been indexed; Thus we need to
45
+ # walk up the parent graph to reindex the parents before we start on the child.
46
+ def recursive_reindex(document, maximum_nesting_depth = max_maximum_nesting_depth)
47
+ return true if processed_ids.include?(document.id)
48
+ raise Exceptions::CycleDetectionError, document.id if maximum_nesting_depth <= 0
49
+ document.parent_ids.each do |parent_id|
50
+ parent_document = @adapter.find_preservation_document_by(id: parent_id)
51
+ recursive_reindex(parent_document, maximum_nesting_depth - 1)
52
+ end
53
+ reindex_a_id(document.id)
54
+ end
55
+
56
+ def reindex_a_id(id)
57
+ id_reindexer.call(id: id)
58
+ processed_ids << id
59
+ rescue StandardError => e
60
+ raise Exceptions::ReindexingError.new(id, e)
61
+ end
62
+ end
63
+ private_constant :RepositoryReindexer
64
+ end
65
+ end
@@ -0,0 +1,5 @@
1
+ module Samvera
2
+ module NestingIndexer
3
+ VERSION = "0.3.0".freeze
4
+ end
5
+ end
@@ -0,0 +1,39 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'samvera/nesting_indexer/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "samvera-nesting_indexer"
7
+ spec.version = Samvera::NestingIndexer::VERSION
8
+ spec.authors = ["Jeremy Friesen"]
9
+ spec.email = ["jeremy.n.friesen@gmail.com"]
10
+
11
+ spec.summary = %q{Samvera nested collections indexing}
12
+ spec.description = %q{Samvera nested collections indexing}
13
+ spec.homepage = "https://github.com/samvera-labs/samvera-nesting_indexer"
14
+ spec.license = "Apache-2.0"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "bin"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+ spec.required_ruby_version = '~>2.0'
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.12"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
25
+ spec.add_development_dependency "rspec-its"
26
+ spec.add_development_dependency "guard-rspec"
27
+ spec.add_development_dependency "guard-rubocop"
28
+ spec.add_development_dependency "terminal-notifier-guard"
29
+ spec.add_development_dependency "terminal-notifier"
30
+ spec.add_development_dependency "rubocop"
31
+ spec.add_development_dependency "simplecov"
32
+ spec.add_development_dependency "codeclimate-test-reporter"
33
+ spec.add_development_dependency "json"
34
+ spec.add_development_dependency "byebug"
35
+ spec.add_development_dependency "railties"
36
+ # As a secondary dependency, listen is preventing bundling
37
+ spec.add_development_dependency "listen", '~> 3.0.8'
38
+ spec.add_dependency "dry-equalizer"
39
+ end
metadata ADDED
@@ -0,0 +1,294 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: samvera-nesting_indexer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Friesen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-its
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: terminal-notifier-guard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: terminal-notifier
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: codeclimate-test-reporter
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: json
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: byebug
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: railties
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: listen
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: 3.0.8
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: 3.0.8
223
+ - !ruby/object:Gem::Dependency
224
+ name: dry-equalizer
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :runtime
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ description: Samvera nested collections indexing
238
+ email:
239
+ - jeremy.n.friesen@gmail.com
240
+ executables: []
241
+ extensions: []
242
+ extra_rdoc_files: []
243
+ files:
244
+ - ".gitignore"
245
+ - ".rspec"
246
+ - ".rubocop.yml"
247
+ - ".rubocop_todo.yml"
248
+ - ".ruby-version"
249
+ - ".travis.yml"
250
+ - Gemfile
251
+ - Guardfile
252
+ - LICENSE
253
+ - README.md
254
+ - Rakefile
255
+ - bin/console
256
+ - bin/setup
257
+ - lib/samvera/nesting_indexer.rb
258
+ - lib/samvera/nesting_indexer/adapters.rb
259
+ - lib/samvera/nesting_indexer/adapters/abstract_adapter.rb
260
+ - lib/samvera/nesting_indexer/adapters/in_memory_adapter.rb
261
+ - lib/samvera/nesting_indexer/adapters/interface_behavior_spec.rb
262
+ - lib/samvera/nesting_indexer/configuration.rb
263
+ - lib/samvera/nesting_indexer/documents.rb
264
+ - lib/samvera/nesting_indexer/exceptions.rb
265
+ - lib/samvera/nesting_indexer/railtie.rb
266
+ - lib/samvera/nesting_indexer/relationship_reindexer.rb
267
+ - lib/samvera/nesting_indexer/repository_reindexer.rb
268
+ - lib/samvera/nesting_indexer/version.rb
269
+ - samvera-nesting_indexer.gemspec
270
+ homepage: https://github.com/samvera-labs/samvera-nesting_indexer
271
+ licenses:
272
+ - Apache-2.0
273
+ metadata: {}
274
+ post_install_message:
275
+ rdoc_options: []
276
+ require_paths:
277
+ - lib
278
+ required_ruby_version: !ruby/object:Gem::Requirement
279
+ requirements:
280
+ - - "~>"
281
+ - !ruby/object:Gem::Version
282
+ version: '2.0'
283
+ required_rubygems_version: !ruby/object:Gem::Requirement
284
+ requirements:
285
+ - - ">="
286
+ - !ruby/object:Gem::Version
287
+ version: '0'
288
+ requirements: []
289
+ rubyforge_project:
290
+ rubygems_version: 2.6.11
291
+ signing_key:
292
+ specification_version: 4
293
+ summary: Samvera nested collections indexing
294
+ test_files: []