wgit 0.11.0 → 0.12.1

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.
@@ -1,679 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../url'
4
- require_relative '../document'
5
- require_relative '../logger'
6
- require_relative '../assertable'
7
- require_relative 'model'
8
- require 'logger'
9
- require 'mongo'
3
+ require_relative "adapters/mongo_db"
10
4
 
11
5
  module Wgit
12
- # Class providing a DB connection and CRUD operations for the Url and
13
- # Document collections.
14
- class Database
15
- include Assertable
6
+ # Module providing a Database connection and CRUD operations for the Url and
7
+ # Document collections that form the Wgit persistence layer.
8
+ module Database
9
+ # The default Database adapter class used by Wgit.
10
+ DEFAULT_ADAPTER_CLASS = Wgit::Database::MongoDB
16
11
 
17
- # The default name of the urls collection.
18
- URLS_COLLECTION = :urls
12
+ # The Database adapter class to be used by Wgit. Set this based on the
13
+ # Database you want to use. The adapter doesn't exist yet? Write your own.
14
+ @adapter_class = DEFAULT_ADAPTER_CLASS
19
15
 
20
- # The default name of the documents collection.
21
- DOCUMENTS_COLLECTION = :documents
22
-
23
- # The default name of the documents collection text search index.
24
- TEXT_INDEX = 'text_search'
25
-
26
- # The default name of the urls and documents collections unique index.
27
- UNIQUE_INDEX = 'unique_url'
28
-
29
- # The documents collection default text search index. Use
30
- # `db.text_index = Wgit::Database::DEFAULT_TEXT_INDEX` to revert changes.
31
- DEFAULT_TEXT_INDEX = {
32
- title: 2,
33
- description: 2,
34
- keywords: 2,
35
- text: 1
36
- }.freeze
37
-
38
- # The connection string for the database.
39
- attr_reader :connection_string
40
-
41
- # The database client object. Gets set when a connection is established.
42
- attr_reader :client
43
-
44
- # The documents collection text index, used to search the DB.
45
- # A custom setter method is also provided for changing the search logic.
46
- attr_reader :text_index
47
-
48
- # The raw MongoDB result of the most recent operation.
49
- attr_reader :last_result
50
-
51
- # Initializes a connected database client using the provided
52
- # connection_string or ENV['WGIT_CONNECTION_STRING'].
53
- #
54
- # @param connection_string [String] The connection string needed to connect
55
- # to the database.
56
- # @raise [StandardError] If a connection string isn't provided, either as a
57
- # parameter or via the environment.
58
- def initialize(connection_string = nil)
59
- connection_string ||= ENV['WGIT_CONNECTION_STRING']
60
- raise "connection_string and ENV['WGIT_CONNECTION_STRING'] are nil" \
61
- unless connection_string
62
-
63
- @client = Database.establish_connection(connection_string)
64
- @connection_string = connection_string
65
- @text_index = DEFAULT_TEXT_INDEX
16
+ class << self
17
+ # The Database adapter class to use with Wgit. The adapter you supply
18
+ # should be a subclass of Wgit::Database::DatabaseAdapter and should
19
+ # implement the methods within it, in order to work with Wgit.
20
+ attr_accessor :adapter_class
66
21
  end
67
22
 
68
- # A class alias for Database.new.
23
+ # Initializes a DatabaseAdapter instance. Is an alias for:
24
+ # `Wgit::Database.adapter_class.new(connection_string)`
69
25
  #
70
26
  # @param connection_string [String] The connection string needed to connect
71
27
  # to the database.
72
28
  # @raise [StandardError] If a connection string isn't provided, either as a
73
29
  # parameter or via the environment.
74
- # @return [Wgit::Database] The connected database client.
75
- def self.connect(connection_string = nil)
76
- new(connection_string)
77
- end
78
-
79
- # Initializes a connected database client using the connection string.
80
- #
81
- # @param connection_string [String] The connection string needed to connect
82
- # to the database.
83
- # @raise [StandardError] If a connection cannot be established.
84
- # @return [Mong::Client] The connected MongoDB client.
85
- def self.establish_connection(connection_string)
86
- # Only log for error (and more severe) scenarios.
87
- Mongo::Logger.logger = Wgit.logger.clone
88
- Mongo::Logger.logger.progname = 'mongo'
89
- Mongo::Logger.logger.level = Logger::ERROR
90
-
91
- # Connects to the database here.
92
- Mongo::Client.new(connection_string)
93
- end
94
-
95
- ### DDL ###
96
-
97
- # Creates the 'urls' and 'documents' collections.
98
- #
99
- # @return [nil] Always returns nil.
100
- def create_collections
101
- @client[URLS_COLLECTION].create
102
- @client[DOCUMENTS_COLLECTION].create
103
-
104
- nil
105
- end
106
-
107
- # Creates the urls and documents unique 'url' indexes.
108
- #
109
- # @return [nil] Always returns nil.
110
- def create_unique_indexes
111
- @client[URLS_COLLECTION].indexes.create_one(
112
- { url: 1 }, name: UNIQUE_INDEX, unique: true
113
- )
114
-
115
- @client[DOCUMENTS_COLLECTION].indexes.create_one(
116
- { 'url.url' => 1 }, name: UNIQUE_INDEX, unique: true
117
- )
118
-
119
- nil
120
- end
121
-
122
- # Set the documents collection text search index aka the fields to #search.
123
- # This is labor intensive on large collections so change little and wisely.
124
- # This method is idempotent in that it will remove the index if it already
125
- # exists before it creates the new index.
126
- #
127
- # @param fields [Array<Symbol>, Hash<Symbol, Integer>] The field names or
128
- # the field names and their coresponding search weights.
129
- # @return [Array<Symbol>, Hash] The passed in value of fields. Use
130
- # `#text_index` to get the new index's fields and weights.
131
- # @raise [StandardError] If fields is of an incorrect type or an error
132
- # occurs with the underlying DB client.
133
- def text_index=(fields)
134
- # We want to end up with a Hash of fields (Symbols) and their
135
- # weights (Integers).
136
- case fields
137
- when Array # of Strings/Symbols.
138
- fields = fields.map { |field| [field.to_sym, 1] }
139
- when Hash # of Strings/Symbols and Integers.
140
- fields = fields.map { |field, weight| [field.to_sym, weight.to_i] }
141
- else
142
- raise "fields must be an Array or Hash, not a #{fields.class}"
143
- end
144
-
145
- fields = fields.to_h
146
- indexes = @client[DOCUMENTS_COLLECTION].indexes
147
-
148
- indexes.drop_one(TEXT_INDEX) if indexes.get(TEXT_INDEX)
149
- indexes.create_one(
150
- fields.map { |field, _| [field, 'text'] }.to_h,
151
- { name: TEXT_INDEX, weights: fields, background: true }
152
- )
153
-
154
- @text_index = fields
155
- end
156
-
157
- ### Create Data ###
158
-
159
- # Insert one or more Url or Document objects into the DB.
160
- #
161
- # @param data [Wgit::Url, Wgit::Document, Enumerable<Wgit::Url,
162
- # Wgit::Document>] The records to insert/create.
163
- # @raise [StandardError] If data isn't valid.
164
- def insert(data)
165
- collection = nil
166
- request_obj = nil
167
-
168
- if data.respond_to?(:map)
169
- request_obj = data.map do |obj|
170
- collection, _, model = get_type_info(obj)
171
- model
172
- end
173
- else
174
- collection, _, model = get_type_info(data)
175
- request_obj = model
176
- end
177
-
178
- create(collection, request_obj)
179
- end
180
-
181
- # Inserts or updates the object in the database.
182
- #
183
- # @param obj [Wgit::Url, Wgit::Document] The obj/record to insert/update.
184
- # @return [Boolean] True if inserted, false if updated.
185
- def upsert(obj)
186
- collection, query, model = get_type_info(obj)
187
- data_hash = model.merge(Wgit::Model.common_update_data)
188
- result = @client[collection].replace_one(query, data_hash, upsert: true)
189
-
190
- result.matched_count.zero?
191
- ensure
192
- @last_result = result
193
- end
194
-
195
- # Bulk upserts the objects in the database collection.
196
- # You cannot mix collection objs types, all must be Urls or Documents.
197
- #
198
- # @param objs [Array<Wgit::Url>, Array<Wgit::Document>] The objs to be
199
- # inserted/updated.
200
- # @return [Integer] The total number of upserted objects.
201
- def bulk_upsert(objs)
202
- assert_arr_types(objs, [Wgit::Url, Wgit::Document])
203
- raise 'objs is empty' if objs.empty?
204
-
205
- collection = nil
206
- request_objs = objs.map do |obj|
207
- collection, query, model = get_type_info(obj)
208
- data_hash = model.merge(Wgit::Model.common_update_data)
209
-
210
- {
211
- update_many: {
212
- filter: query,
213
- update: { '$set' => data_hash },
214
- upsert: true
215
- }
216
- }
217
- end
218
-
219
- result = @client[collection].bulk_write(request_objs)
220
- result.upserted_count + result.modified_count
221
- ensure
222
- @last_result = result
223
- end
224
-
225
- ### Retrieve Data ###
226
-
227
- # Returns all Document records from the DB. Use #search to filter based on
228
- # the text_index of the collection.
229
- #
230
- # All Documents are sorted by date_added ascending, in other words the
231
- # first doc returned is the first one that was inserted into the DB.
232
- #
233
- # @param limit [Integer] The max number of returned records. 0 returns all.
234
- # @param skip [Integer] Skip n records.
235
- # @yield [doc] Given each Document object (Wgit::Document) returned from
236
- # the DB.
237
- # @return [Array<Wgit::Document>] The Documents obtained from the DB.
238
- def docs(limit: 0, skip: 0, &block)
239
- results = retrieve(DOCUMENTS_COLLECTION, {},
240
- sort: { date_added: 1 }, limit:, skip:)
241
- return [] if results.count < 1 # results#empty? doesn't exist.
242
-
243
- # results.respond_to? :map! is false so we use map and overwrite the var.
244
- results = results.map { |doc_hash| Wgit::Document.new(doc_hash) }
245
- results.each(&block) if block_given?
246
-
247
- results
248
- end
249
-
250
- # Returns all Url records from the DB.
251
- #
252
- # All Urls are sorted by date_added ascending, in other words the first url
253
- # returned is the first one that was inserted into the DB.
254
- #
255
- # @param crawled [Boolean] Filter by Url#crawled value. nil returns all.
256
- # @param limit [Integer] The max number of Url's to return. 0 returns all.
257
- # @param skip [Integer] Skip n amount of Url's.
258
- # @yield [url] Given each Url object (Wgit::Url) returned from the DB.
259
- # @return [Array<Wgit::Url>] The Urls obtained from the DB.
260
- def urls(crawled: nil, limit: 0, skip: 0, &block)
261
- query = crawled.nil? ? {} : { crawled: }
262
- sort = { date_added: 1 }
263
-
264
- results = retrieve(URLS_COLLECTION, query, sort:, limit:, skip:)
265
- return [] if results.count < 1 # results#empty? doesn't exist.
266
-
267
- # results.respond_to? :map! is false so we use map and overwrite the var.
268
- results = results.map { |url_doc| Wgit::Url.new(url_doc) }
269
- results.each(&block) if block_given?
270
-
271
- results
272
- end
273
-
274
- # Returns Url records that have been crawled.
275
- #
276
- # @param limit [Integer] The max number of Url's to return. 0 returns all.
277
- # @param skip [Integer] Skip n amount of Url's.
278
- # @yield [url] Given each Url object (Wgit::Url) returned from the DB.
279
- # @return [Array<Wgit::Url>] The crawled Urls obtained from the DB.
280
- def crawled_urls(limit: 0, skip: 0, &block)
281
- urls(crawled: true, limit:, skip:, &block)
282
- end
283
-
284
- # Returned Url records that haven't yet been crawled.
285
- #
286
- # @param limit [Integer] The max number of Url's to return. 0 returns all.
287
- # @param skip [Integer] Skip n amount of Url's.
288
- # @yield [url] Given each Url object (Wgit::Url) returned from the DB.
289
- # @return [Array<Wgit::Url>] The uncrawled Urls obtained from the DB.
290
- def uncrawled_urls(limit: 0, skip: 0, &block)
291
- urls(crawled: false, limit:, skip:, &block)
292
- end
293
-
294
- # Searches the database's Documents for the given query.
295
- #
296
- # The searched fields are decided by the text index setup on the
297
- # documents collection. Currently we search against the following fields:
298
- # "author", "keywords", "title" and "text" by default.
299
- #
300
- # The MongoDB search algorithm ranks/sorts the results in order (highest
301
- # first) based on each document's "textScore" (which records the number of
302
- # query hits). The "textScore" is then stored in each Document result
303
- # object for use elsewhere if needed; accessed via Wgit::Document#score.
304
- #
305
- # @param query [String] The text query to search with.
306
- # @param case_sensitive [Boolean] Whether character case must match.
307
- # @param whole_sentence [Boolean] Whether multiple words should be searched
308
- # for separately.
309
- # @param limit [Integer] The max number of results to return.
310
- # @param skip [Integer] The number of results to skip.
311
- # @yield [doc] Given each search result (Wgit::Document) returned from the
312
- # DB.
313
- # @return [Array<Wgit::Document>] The search results obtained from the DB.
314
- def search(
315
- query, case_sensitive: false, whole_sentence: true, limit: 10, skip: 0
316
- )
317
- query = query.to_s.strip
318
- query.replace("\"#{query}\"") if whole_sentence
319
-
320
- # Sort based on the most search hits (aka "textScore").
321
- # We use the sort_proj hash as both a sort and a projection below.
322
- sort_proj = { score: { :$meta => 'textScore' } }
323
- query = {
324
- :$text => {
325
- :$search => query,
326
- :$caseSensitive => case_sensitive
327
- }
328
- }
329
-
330
- results = retrieve(DOCUMENTS_COLLECTION, query,
331
- sort: sort_proj, projection: sort_proj,
332
- limit:, skip:)
333
-
334
- results.map do |mongo_doc|
335
- doc = Wgit::Document.new(mongo_doc)
336
- yield(doc) if block_given?
337
- doc
338
- end
339
- end
340
-
341
- # Searches the database's Documents for the given query and then searches
342
- # each result in turn using `doc.search!`. This method is therefore the
343
- # equivalent of calling `Wgit::Database#search` and then
344
- # `Wgit::Document#search!` in turn. See their documentation for more info.
345
- #
346
- # @param query [String] The text query to search with.
347
- # @param case_sensitive [Boolean] Whether character case must match.
348
- # @param whole_sentence [Boolean] Whether multiple words should be searched
349
- # for separately.
350
- # @param limit [Integer] The max number of results to return.
351
- # @param skip [Integer] The number of results to skip.
352
- # @param sentence_limit [Integer] The max length of each search result
353
- # sentence.
354
- # @yield [doc] Given each search result (Wgit::Document) returned from the
355
- # DB having called `doc.search!(query)`.
356
- # @return [Array<Wgit::Document>] The search results obtained from the DB
357
- # having called `doc.search!(query)`.
358
- def search!(
359
- query, case_sensitive: false, whole_sentence: true,
360
- limit: 10, skip: 0, sentence_limit: 80
361
- )
362
- results = search(query, case_sensitive:, whole_sentence:, limit:, skip:)
363
-
364
- results.each do |doc|
365
- doc.search!(query, case_sensitive:, whole_sentence:, sentence_limit:)
366
- yield(doc) if block_given?
367
- end
368
-
369
- results
370
- end
371
-
372
- # Searches the database's Documents for the given query and then searches
373
- # each result in turn using `doc.search`. Instead of an Array of Documents,
374
- # this method returns a Hash of the docs url => search_results creating a
375
- # search engine like result set for quick access to text matches.
376
- #
377
- # @param query [String] The text query to search with.
378
- # @param case_sensitive [Boolean] Whether character case must match.
379
- # @param whole_sentence [Boolean] Whether multiple words should be searched
380
- # for separately.
381
- # @param limit [Integer] The max number of results to return.
382
- # @param skip [Integer] The number of results to skip.
383
- # @param sentence_limit [Integer] The max length of each search result
384
- # sentence.
385
- # @param top_result_only [Boolean] Whether to return all of the documents
386
- # search results or just the top (most relavent) result.
387
- # @yield [doc] Given each search result (Wgit::Document) returned from the
388
- # DB.
389
- # @return [Hash<String, String | Array<String>>] The search results obtained
390
- # from the DB having mapped the docs url => search_results. The format of
391
- # search_results depends on the value of `top_result_only`.
392
- def search_text(
393
- query, case_sensitive: false, whole_sentence: true,
394
- limit: 10, skip: 0, sentence_limit: 80, top_result_only: false
395
- )
396
- results = search(query, case_sensitive:, whole_sentence:, limit:, skip:)
397
-
398
- results
399
- .map do |doc|
400
- yield(doc) if block_given?
401
-
402
- # Only return result if its text has a match - compact is called below.
403
- results = doc.search(
404
- query, case_sensitive:, whole_sentence:, sentence_limit:
405
- )
406
- next nil if results.empty?
407
-
408
- [doc.url, (top_result_only ? results.first : results)]
409
- end
410
- .compact
411
- .to_h
412
- end
413
-
414
- # Returns statistics about the database.
415
- #
416
- # @return [BSON::Document#[]#fetch] Similar to a Hash instance.
417
- def stats
418
- @client.command(dbStats: 0).documents[0]
419
- end
420
-
421
- # Returns the current size of the database.
422
- #
423
- # @return [Integer] The current size of the DB.
424
- def size
425
- stats[:dataSize]
426
- end
427
-
428
- # Returns the total number of URL records in the DB.
429
- #
430
- # @return [Integer] The current number of URL records.
431
- def num_urls
432
- @client[URLS_COLLECTION].count
433
- end
434
-
435
- # Returns the total number of Document records in the DB.
436
- #
437
- # @return [Integer] The current number of Document records.
438
- def num_docs
439
- @client[DOCUMENTS_COLLECTION].count
440
- end
441
-
442
- # Returns the total number of records (urls + docs) in the DB.
443
- #
444
- # @return [Integer] The current number of URL and Document records.
445
- def num_records
446
- num_urls + num_docs
447
- end
448
-
449
- # Returns whether or not a record with the given 'url' field (which is
450
- # unique) exists in the database's 'urls' collection.
451
- #
452
- # @param url [Wgit::Url] The Url to search the DB for.
453
- # @return [Boolean] True if url exists, otherwise false.
454
- def url?(url)
455
- assert_type(url, String) # This includes Wgit::Url's.
456
- query = { url: }
457
- retrieve(URLS_COLLECTION, query, limit: 1).any?
458
- end
459
-
460
- # Returns whether or not a record with the given doc 'url.url' field
461
- # (which is unique) exists in the database's 'documents' collection.
462
- #
463
- # @param doc [Wgit::Document] The Document to search the DB for.
464
- # @return [Boolean] True if doc exists, otherwise false.
465
- def doc?(doc)
466
- assert_type(doc, Wgit::Document)
467
- query = { 'url.url' => doc.url }
468
- retrieve(DOCUMENTS_COLLECTION, query, limit: 1).any?
469
- end
470
-
471
- # Returns if a record exists with the given obj's url.
472
- #
473
- # @param obj [Wgit::Url, Wgit::Document] Object containing the url to
474
- # search for.
475
- # @return [Boolean] True if a record exists with the url, false otherwise.
476
- def exists?(obj)
477
- obj.is_a?(String) ? url?(obj) : doc?(obj)
478
- end
479
-
480
- # Returns a record from the database with the matching 'url' field; or nil.
481
- # Pass either a Wgit::Url or Wgit::Document instance.
482
- #
483
- # @param obj [Wgit::Url, Wgit::Document] The record to search the DB for.
484
- # @return [Wgit::Url, Wgit::Document, nil] The record with the matching
485
- # 'url' field or nil if no results can be found.
486
- def get(obj)
487
- collection, query = get_type_info(obj)
488
-
489
- record = retrieve(collection, query, limit: 1).first
490
- return nil unless record
491
-
492
- obj.class.new(record)
493
- end
494
-
495
- ### Update Data ###
496
-
497
- # Update a Url or Document object in the DB.
498
- #
499
- # @param obj [Wgit::Url, Wgit::Document] The obj/record to update.
500
- # @raise [StandardError] If the obj is not valid.
501
- # @return [Integer] The number of updated records/objects.
502
- def update(obj)
503
- collection, query, model = get_type_info(obj)
504
- data_hash = model.merge(Wgit::Model.common_update_data)
505
-
506
- mutate(collection, query, { '$set' => data_hash })
507
- end
508
-
509
- ### Delete Data ###
510
-
511
- # Deletes a record from the database with the matching 'url' field.
512
- # Pass either a Wgit::Url or Wgit::Document instance.
513
- #
514
- # @param obj [Wgit::Url, Wgit::Document] The record to search the DB for
515
- # and delete.
516
- # @return [Integer] The number of records deleted - should always be
517
- # 0 or 1 because urls are unique.
518
- def delete(obj)
519
- collection, query = get_type_info(obj)
520
- result = @client[collection].delete_one(query)
521
- result.n
522
- ensure
523
- @last_result = result
30
+ def self.new(connection_string = nil)
31
+ Wgit::Database.adapter_class.new(connection_string)
524
32
  end
525
-
526
- # Deletes everything in the urls collection.
527
- #
528
- # @return [Integer] The number of deleted records.
529
- def clear_urls
530
- result = @client[URLS_COLLECTION].delete_many({})
531
- result.n
532
- ensure
533
- @last_result = result
534
- end
535
-
536
- # Deletes everything in the documents collection.
537
- #
538
- # @return [Integer] The number of deleted records.
539
- def clear_docs
540
- result = @client[DOCUMENTS_COLLECTION].delete_many({})
541
- result.n
542
- ensure
543
- @last_result = result
544
- end
545
-
546
- # Deletes everything in the urls and documents collections. This will nuke
547
- # the entire database so yeah... be careful.
548
- #
549
- # @return [Integer] The number of deleted records.
550
- def clear_db
551
- clear_urls + clear_docs
552
- end
553
-
554
- private
555
-
556
- # Get the database's type info (collection type, query hash, model) for
557
- # obj.
558
- #
559
- # Raises an error if obj isn't a Wgit::Url or Wgit::Document.
560
- # Note, that no database calls are made during this method call.
561
- #
562
- # @param obj [Wgit::Url, Wgit::Document] The obj to get semantics for.
563
- # @raise [StandardError] If obj isn't a Wgit::Url or Wgit::Document.
564
- # @return [Array<Symbol, Hash>] The collection type, query to get
565
- # the record/obj from the database (if it exists) and the model of obj.
566
- def get_type_info(obj)
567
- obj = obj.dup
568
-
569
- case obj
570
- when Wgit::Url
571
- collection = URLS_COLLECTION
572
- query = { url: obj.to_s }
573
- model = Wgit::Model.url(obj)
574
- when Wgit::Document
575
- collection = DOCUMENTS_COLLECTION
576
- query = { 'url.url' => obj.url.to_s }
577
- model = Wgit::Model.document(obj)
578
- else
579
- raise "obj must be a Wgit::Url or Wgit::Document, not: #{obj.class}"
580
- end
581
-
582
- [collection, query, model]
583
- end
584
-
585
- # Create/insert one or more Url or Document records into the DB.
586
- #
587
- # @param collection [Symbol] Either :urls or :documents.
588
- # @param data [Hash, Array<Hash>] The data to insert.
589
- # @raise [StandardError] If data type is unsupported or the write fails.
590
- # @return [Integer] The number of inserted records.
591
- def create(collection, data)
592
- assert_types(data, [Hash, Array])
593
-
594
- case data
595
- when Hash # Single record.
596
- data.merge!(Wgit::Model.common_insert_data)
597
- result = @client[collection.to_sym].insert_one(data)
598
- raise 'DB write (insert) failed' unless write_succeeded?(result)
599
-
600
- result.n
601
- when Array # Multiple records.
602
- assert_arr_type(data, Hash)
603
- data.map! { |hash| hash.merge(Wgit::Model.common_insert_data) }
604
- result = @client[collection.to_sym].insert_many(data)
605
- unless write_succeeded?(result, num_writes: data.length)
606
- raise 'DB write(s) (insert) failed'
607
- end
608
-
609
- result.inserted_count
610
- else
611
- raise 'data must be a Hash or an Array of Hashes'
612
- end
613
- ensure
614
- @last_result = result
615
- end
616
-
617
- # Return if the write to the DB succeeded or not.
618
- #
619
- # @param result [Mongo::Collection::View] The write result.
620
- # @param num_writes [Integer] The number of records written to.
621
- # @raise [StandardError] If the result type isn't supported.
622
- # @return [Boolean] True if the write was successful, false otherwise.
623
- def write_succeeded?(result, num_writes: 1)
624
- case result
625
- when Mongo::Operation::Insert::Result # Single create result.
626
- result.documents.first[:err].nil?
627
- when Mongo::BulkWrite::Result # Multiple create result.
628
- result.inserted_count == num_writes
629
- when Mongo::Operation::Update::Result # Single/multiple update result.
630
- singleton = (num_writes == 1)
631
- singleton ? result.documents.first[:err].nil? : result.n == num_writes
632
- else # Class no longer used, have you upgraded the 'mongo' gem?
633
- raise "Result class not currently supported: #{result.class}"
634
- end
635
- end
636
-
637
- # Retrieve Url or Document records from the DB.
638
- #
639
- # @param collection [Symbol] Either :urls or :documents.
640
- # @param query [Hash] The query used for the retrieval.
641
- # @param sort [Hash] The sort to use.
642
- # @param projection [Hash] The projection to use.
643
- # @param limit [Integer] The limit to use.
644
- # @param skip [Integer] The skip to use.
645
- # @raise [StandardError] If query type isn't valid.
646
- # @return [Mongo::Collection::View] The retrieval viewset.
647
- def retrieve(collection, query,
648
- sort: {}, projection: {},
649
- limit: 0, skip: 0)
650
- assert_type(query, Hash)
651
- @last_result = @client[collection.to_sym].find(query).projection(projection)
652
- .skip(skip).limit(limit).sort(sort)
653
- end
654
-
655
- # Mutate/update one or more Url or Document records in the DB.
656
- #
657
- # This method expects Model.common_update_data to have been merged in
658
- # already by the calling method.
659
- #
660
- # @param collection [Symbol] Either :urls or :documents.
661
- # @param query [Hash] The query used for the retrieval before updating.
662
- # @param update [Hash] The updated/new object.
663
- # @raise [StandardError] If the update fails.
664
- # @return [Integer] The number of updated records/objects.
665
- def mutate(collection, query, update)
666
- assert_arr_type([query, update], Hash)
667
-
668
- result = @client[collection.to_sym].update_one(query, update)
669
- raise 'DB write(s) (update) failed' unless write_succeeded?(result)
670
-
671
- result.n
672
- ensure
673
- @last_result = result
674
- end
675
-
676
- alias_method :num_objects, :num_records
677
- alias_method :clear_db!, :clear_db
678
33
  end
679
34
  end